<?php

namespace FleekDash\Core;

use FleekDash\Traits\Base;

/**
 * Class Logger
 *
 * Handles error logging and display for the plugin
 *
 * @package FleekDash\Core
 */
class Logger
{
    use Base;

    /**
     * Log levels
     */
    const LEVEL_DEBUG = 'debug';
    const LEVEL_INFO = 'info';
    const LEVEL_WARNING = 'warning';
    const LEVEL_ERROR = 'error';
    const LEVEL_FATAL = 'fatal';

    /**
     * Context flag: fatal was captured in register_shutdown_function (any plugin/theme may be the source).
     */
    public const CONTEXT_SHUTDOWN_OBSERVER = 'shutdown_observer';

    private const ON_LINE = ' on line ';

    /**
     * Logs a message with the specified level
     *
     * @param string $message The message to log
     * @param string $level The log level
     * @param array $context Additional context data
     * @return void
     */
    public function log($message, $level = self::LEVEL_INFO, $context = [])
    {
        $isShutdownObserver = !empty($context[self::CONTEXT_SHUTDOWN_OBSERVER]);
        $logTag = ($isShutdownObserver && $level === self::LEVEL_FATAL)
            ? 'php_fatal_observed'
            : $level;

        // Always log to error_log
        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
            $contextForLog = $context;
            if ($isShutdownObserver) {
                unset($contextForLog[self::CONTEXT_SHUTDOWN_OBSERVER]);
            }
            $context_str = !empty($contextForLog) ? ' Context: ' . json_encode($contextForLog) : '';
            error_log("[FleekDash][$logTag] $message" . $context_str);
        }

        // For errors and fatal errors, consider additional actions
        $hasException = !empty($context['exception']) && $context['exception'] instanceof \Exception;
        if (($level === self::LEVEL_ERROR || $level === self::LEVEL_FATAL) && $hasException) {
            $exception = $context['exception'];
            error_log("[FleekDash][$logTag] Exception: " . $exception->getMessage());
            error_log("[FleekDash][$logTag] File: " . $exception->getFile() . self::ON_LINE . $exception->getLine());
            error_log("[FleekDash][$logTag] Trace: " . $exception->getTraceAsString());
        }
    }

    /**
     * Logs a debug message
     *
     * @param string $message The message to log
     * @param array $context Additional context data
     * @return void
     */
    public function debug($message, $context = [])
    {
        $this->log($message, self::LEVEL_DEBUG, $context);
    }

    /**
     * Logs an info message
     *
     * @param string $message The message to log
     * @param array $context Additional context data
     * @return void
     */
    public function info($message, $context = [])
    {
        $this->log($message, self::LEVEL_INFO, $context);
    }

    /**
     * Logs a warning message
     *
     * @param string $message The message to log
     * @param array $context Additional context data
     * @return void
     */
    public function warning($message, $context = [])
    {
        $this->log($message, self::LEVEL_WARNING, $context);
    }

    /**
     * Logs an error message
     *
     * @param string $message The message to log
     * @param array $context Additional context data
     * @return void
     */
    public function error($message, $context = [])
    {
        $this->log($message, self::LEVEL_ERROR, $context);
    }

    /**
     * Logs a fatal error message
     *
     * @param string $message The message to log
     * @param array $context Additional context data
     * @return void
     */
    public function fatal($message, $context = [])
    {
        $this->log($message, self::LEVEL_FATAL, $context);

        // If we're in debug mode and fatal errors should be displayed, show the error
        if (!defined('WP_DEBUG') || !WP_DEBUG || !defined('WP_DEBUG_DISPLAY') || !WP_DEBUG_DISPLAY) {
            return;
        }

        $error_html = $this->buildFatalErrorDisplayHtml($message, $context);

        if (!headers_sent()) {
            status_header(500);
            header('Content-Type: text/html; charset=utf-8');
        }

        echo $error_html;
    }

    /**
     * @param array<string, mixed> $context
     */
    private function buildFatalErrorDisplayHtml(string $message, array $context): string
    {
        $isShutdownObserver = !empty($context[self::CONTEXT_SHUTDOWN_OBSERVER]);
        $error_html = '<div class="fleekdash-fatal-error">';
        if ($isShutdownObserver) {
            $error_html .= '<h3>' . \esc_html__('Uncaught PHP fatal (diagnostic)', 'fleekdash') . '</h3>';
            $error_html .= '<p>' . \esc_html__(
                'FleekDash recorded this at the end of the request for debugging. The fault is usually in another plugin or theme (see file below), not in FleekDash.',
                'fleekdash'
            ) . '</p>';
        } else {
            $error_html .= '<h3>' . \esc_html__('FleekDash Fatal Error', 'fleekdash') . '</h3>';
        }
        $error_html .= '<p>' . \esc_html($message) . '</p>';

        if ($isShutdownObserver && !empty($context['file'])) {
            $error_html .= '<p>File: ' . \esc_html((string) $context['file']) . self::ON_LINE . \esc_html((string) ($context['line'] ?? '')) . '</p>';
        }

        if (!empty($context['exception']) && $context['exception'] instanceof \Exception) {
            $exception = $context['exception'];
            $error_html .= '<p>File: ' . \esc_html($exception->getFile()) . self::ON_LINE . \esc_html($exception->getLine()) . '</p>';

            if (defined('WP_DEBUG') && WP_DEBUG) {
                $error_html .= '<pre>' . \esc_html($exception->getTraceAsString()) . '</pre>';
            }
        }

        $error_html .= '</div>';

        return $error_html;
    }

    /**
     * Helper to handle exceptions uniformly
     *
     * @param \Exception $exception The exception to handle
     * @param bool $is_fatal Whether this is a fatal exception
     * @return void
     */
    public function exception(\Exception $exception, $is_fatal = false)
    {
        $context = ['exception' => $exception];

        if ($is_fatal) {
            $this->fatal($exception->getMessage(), $context);
        } else {
            $this->error($exception->getMessage(), $context);
        }
    }
}
