<?php
declare(strict_types=1);

namespace CloudXM\SDK\Bootstrap;

use CloudXM\SDK\Logger;
use Throwable;

final class ErrorMonitor
{
    /** prevent double-install */
    private static bool $installed = false;

    /** minimal loop guard / rate limit */
    private static int $errorsSeen = 0;
    private const ERROR_CAP = 200; // per request

    /**
     * Register PHP error/exception/shutdown handlers.
     * Call this once during plugin bootstrap (e.g. on 'plugins_loaded').
     */
    public static function register(): void
    {
        if (self::$installed) {
            return;
        }
        self::$installed = true;

        // Convert PHP errors → log
        set_error_handler([self::class, 'onPhpError']);

        // Uncaught exceptions → log
        set_exception_handler([self::class, 'onException']);

        // Fatal errors on shutdown → log
        register_shutdown_function([self::class, 'onShutdown']);
    }

    /** Convert PHP errors to log records with sane mapping */
    public static function onPhpError(int $errno, string $errstr, string $errfile = '', int $errline = 0): bool
    {
        // error_log('onPhpError - ' . $errno . ' - ' . $errstr . ' - ' . $errfile);
        if (self::$errorsSeen++ > self::ERROR_CAP) {
            return false; // let PHP handle; avoid log storms
        }

        // Respect current error_reporting mask
        if (!(error_reporting() & $errno)) {
            return false;
        }

        // Filter out harmless EXIF warnings from WordPress image processing
        if (strpos($errstr, 'exif_read_data') !== false && strpos($errstr, 'Incorrect APP1 Exif Identifier Code') !== false) {
            return false; // Suppress this harmless warning
        }

        // Check if this PHP error is in the configured whitelist before proceeding
        $config = \CloudXM\SDK\Utilities\Config::getInstance();
        $wpErrorWhitelist = $config->get('logging.wp_error_whitelist', []);

        // For PHP errors, we need to map them to meaning codes for whitelist checking
        $errorCode = self::mapErrorToCode($errno, $errstr);

        // Check if this error type is configured to be logged
        $config = \CloudXM\SDK\Utilities\Config::getInstance();
        $allowedTypes = $config->get('logging.php_error_types', [E_ERROR]); // Default to only fatal errors

        [$level, $remote] = self::mapPhpError($errno);

        // Check WordPress Error Minimum Level for suppression/severity decisions
        $wpErrorMinLevel = $config->get('logging.wp_error_min_level', 'error');
        $levelPriority = ['debug' => 0, 'info' => 1, 'warn' => 2, 'error' => 3, 'critical' => 4, 'notice' => -1]; // notice is below debug
        $currentPriority = $levelPriority[$level] ?? 1;
        $wpErrorMinPriority = $levelPriority[$wpErrorMinLevel] ?? 3; // default to error level

        // Suppress entirely if error level is below minimum level
        if ($currentPriority < $wpErrorMinPriority) {
            return false; // Suppress entirely - below minimum level
        }

        $ctx = [
            'errno' => $errno,
            'file'  => $errfile,
            'line'  => $errline,
        ];

        // Set sink to internal if not whitelisted OR not in allowed error types
        // This ensures logs are ALWAYS written locally, but remote routing is controlled
        $shouldBeInternal = false;

        // Check whitelist - mark as internal if not whitelisted
        if (!empty($wpErrorWhitelist) && !in_array($errorCode, $wpErrorWhitelist, true)) {
            $shouldBeInternal = true;
        }

        // Check error types - mark as internal if not allowed
        if (!in_array($errno, $allowedTypes, true)) {
            $shouldBeInternal = true;
        }

        if ($shouldBeInternal) {
            $ctx['sink'] = 'internal'; // Log to file only, not Grafana
        }

        // Use facade; channel is inferred from backtrace/file
        Logger::log($level, $errstr, $ctx);


        // In CLI context, fatal errors should cause command failure
        if (defined('WP_CLI') && WP_CLI && in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            // Log the error and ensure it gets to error_log for CLI visibility
            error_log("Fatal PHP error in WP CLI: {$errstr}");
            // Force exit with error code to ensure command fails
            exit(1);
        }

        // Return false to allow normal PHP handling (e.g., display_errors in dev)
        return false;
    }

    /** Uncaught exceptions */
    public static function onException(Throwable $e): void
    {
        // Check WordPress Error Minimum Level for suppression/severity decisions
        $config = \CloudXM\SDK\Utilities\Config::getInstance();
        $wpErrorMinLevel = $config->get('logging.wp_error_min_level', 'error');
        $levelPriority = ['debug' => 0, 'info' => 1, 'warn' => 2, 'error' => 3, 'critical' => 4, 'notice' => -1];
        $errorPriority = $levelPriority['error'] ?? 3; // Exceptions are always 'error' level
        $wpErrorMinPriority = $levelPriority[$wpErrorMinLevel] ?? 3; // default to error level

        // Suppress entirely if error level is below minimum level
        if ($errorPriority < $wpErrorMinPriority) {
            return; // Suppress entirely - below minimum level
        }

        $ctx = [
            'ex'   => get_class($e),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'code' => $e->getCode(),
        ];

        // Get whitelist for checking if exception should go to remote
        $wpErrorWhitelist = $config->get('logging.wp_error_whitelist', []);

        // For exceptions, check if generic error codes are whitelisted
        $shouldBeInternal = false;
        $allowedExceptionCodes = ['php_fatal_error', 'php_error', 'exception']; // Common exception codes

        $hasAllowedCode = false;
        foreach ($allowedExceptionCodes as $code) {
            if (in_array($code, $wpErrorWhitelist, true)) {
                $hasAllowedCode = true;
                break;
            }
        }

        // If no exception codes are whitelisted, mark as internal-only
        if (!$hasAllowedCode) {
            $shouldBeInternal = true;
        }

        if ($shouldBeInternal) {
            $ctx['sink'] = 'internal'; // Log to file only, not Grafana
        }

        // In CLI context, ensure exceptions cause command failure
        if (defined('WP_CLI') && WP_CLI) {
            // Log the error and ensure it gets to error_log for CLI visibility
            error_log("Uncaught exception in WP CLI: " . $e->getMessage());
            // Re-throw the exception to ensure command fails
            throw $e;
        }

        // Message only; avoid dumping full traces unless you really need them
        Logger::error($e->getMessage(), $ctx);
    }

    /** Fatal errors at shutdown */
    public static function onShutdown(): void
    {
        $last = error_get_last();
        if (!$last) {
            return;
        }

        $config = \CloudXM\SDK\Utilities\Config::getInstance();
        $wpErrorWhitelist = $config->get('logging.wp_error_whitelist', []);

        // For shutdown errors, we need to map them to meaning codes
        $errno = $last['type'] ?? E_ERROR;
        $message = $last['message'] ?? 'Fatal error on shutdown';
        $errorCode = self::mapErrorToCode($errno, $message);

        [$level, $remote] = self::mapPhpError($errno);

        // Check WordPress Error Minimum Level for suppression/severity decisions
        $wpErrorMinLevel = $config->get('logging.wp_error_min_level', 'error');
        $levelPriority = ['debug' => 0, 'info' => 1, 'warn' => 2, 'error' => 3, 'critical' => 4, 'notice' => -1];
        $currentPriority = $levelPriority[$level] ?? 1;
        $wpErrorMinPriority = $levelPriority[$wpErrorMinLevel] ?? 3; // default to error level

        // Suppress entirely if error level is below minimum level
        if ($currentPriority < $wpErrorMinPriority) {
            error_log($message);
            return; // Suppress entirely - below minimum level
        }

        $ctx = [
            'errno' => $errno,
            'file'  => $last['file'] ?? null,
            'line'  => $last['line'] ?? null,
        ];

        // Set sink to internal if not whitelisted
        if (!in_array($errorCode, $wpErrorWhitelist, true)) {
            $ctx['sink'] = 'internal'; // Log to file only, not Grafana
        }

        // In CLI context, fatal errors should cause command failure
        if (defined('WP_CLI') && WP_CLI && in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            // Log the error but also ensure it gets to error_log for CLI visibility
            error_log("Fatal error in WP CLI: {$message}");
            // Force exit with error code to ensure command fails
            exit(1);
        }
    }

    /**
     * Map PHP error types to PSR-3 levels and whether to send remote.
     * Remote = dual-sink (file + Loki) via your LoggerFactory policy.
     */
    private static function mapPhpError(int $errno): array
    {
        // default: internal only
        $remote = false;
        $level  = 'warning';

        switch ($errno) {
            case E_ERROR:
            case E_USER_ERROR:
            case E_RECOVERABLE_ERROR:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
                $level = 'error';
                $remote = true;
                break;

            case E_PARSE:
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
                $level = 'critical';
                $remote = true;
                break;

            case E_WARNING:
            case E_USER_WARNING:
                $level = 'warning';
                break;

            case E_NOTICE:
            case E_USER_NOTICE:
            case E_STRICT:
                $level = 'notice';
                break;

            case E_DEPRECATED:
            case E_USER_DEPRECATED:
                $level = 'notice';
                break;

            default:
                $level = 'warning';
                break;
        }

        return [$level, $remote];
    }

    /**
     * Map PHP error to a meaningful code for whitelist filtering
     */
    private static function mapErrorToCode(int $errno, string $errstr): string
    {
        // Special mappings for known error types
        if (strpos($errstr, '_load_textdomain_just_in_time') !== false) {
            return '_load_textdomain_just_in_time';
        }

        if (strpos($errstr, 'exif_read_data') !== false && strpos($errstr, 'Incorrect APP1 Exif Identifier Code') !== false) {
            return 'exif_read_data';
        }

        // Generic mappings based on error type
        switch ($errno) {
            case E_ERROR:
            case E_USER_ERROR:
                return 'php_fatal_error';
            case E_WARNING:
            case E_USER_WARNING:
                return 'php_warning';
            case E_NOTICE:
            case E_USER_NOTICE:
                return 'php_notice';
            case E_STRICT:
                return 'php_strict';
            case E_DEPRECATED:
            case E_USER_DEPRECATED:
                return 'php_deprecated';
            case E_RECOVERABLE_ERROR:
                return 'php_recoverable_error';
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_PARSE:
                return 'php_core_error';
            default:
                return 'php_unknown_error';
        }
    }
}