<?php
/**
 * Global Error & Exception Handler
 * Intercepts PHP errors, uncaught exceptions, and fatal shutdown errors
 * Provides structured JSON responses for APIs and elegant views for web users
 * Masks system paths and SQL details in production
 */

require_once __DIR__ . '/Exceptions/AppException.php';
require_once __DIR__ . '/Exceptions/ValidationException.php';
require_once __DIR__ . '/Exceptions/AuthenticationException.php';
require_once __DIR__ . '/Exceptions/AuthorizationException.php';
require_once __DIR__ . '/Exceptions/NotFoundException.php';
require_once __DIR__ . '/Exceptions/ConflictException.php';
require_once __DIR__ . '/Exceptions/RateLimitException.php';
require_once __DIR__ . '/Exceptions/BusinessRuleException.php';
require_once __DIR__ . '/Exceptions/CsrfException.php';
require_once __DIR__ . '/../Helpers/Logger.php';
require_once __DIR__ . '/../Helpers/Security.php';

class ErrorHandler {
    private static bool $registered = false;

    public static function register(): void {
        if (self::$registered) {
            return;
        }

        // Set error reporting
        error_reporting(E_ALL);

        // Register Handlers
        set_error_handler([self::class, 'handleError']);
        set_exception_handler([self::class, 'handleException']);
        register_shutdown_function([self::class, 'handleShutdown']);

        self::$registered = true;
    }

    /**
     * Convert PHP Errors to ErrorException
     */
    public static function handleError(int $severity, string $message, string $file, int $line): bool {
        if (!(error_reporting() & $severity)) {
            return false;
        }
        throw new ErrorException($message, 0, $severity, $file, $line);
    }

    /**
     * Uncaught Exception Handler
     */
    public static function handleException(Throwable $e): void {
        $statusCode = 500;
        $errorCode = 'INTERNAL_SERVER_ERROR';
        $userMessage = 'เกิดข้อผิดพลาดขึ้นในระบบ กรุณาลองใหม่อีกครั้ง';
        $details = [];

        if ($e instanceof AppException) {
            $statusCode = $e->getStatusCode();
            $errorCode = $e->getErrorCode();
            $userMessage = $e->getMessage();
            $details = $e->getContext();

            if ($e instanceof ValidationException) {
                $details = ['errors' => $e->getErrors()];
            } elseif ($e instanceof RateLimitException) {
                header('Retry-After: ' . $e->getRetryAfter());
            }
        } elseif ($e instanceof PDOException) {
            $statusCode = 500;
            $errorCode = 'DATABASE_ERROR';
            $userMessage = 'เกิดข้อผิดพลาดในการเชื่อมต่อฐานข้อมูล กรุณาลองใหม่อีกครั้ง';
            Logger::critical("Database Exception: " . $e->getMessage(), [
                'code'  => $e->getCode(),
                'trace' => $e->getTraceAsString()
            ]);
        } else {
            Logger::error("Unhandled Exception: " . $e->getMessage(), [
                'type'  => get_class($e),
                'file'  => $e->getFile(),
                'line'  => $e->getLine(),
                'trace' => $e->getTraceAsString()
            ]);
        }

        // Check if debug mode is enabled
        $appConfig = file_exists(__DIR__ . '/../../config/app.php') ? require __DIR__ . '/../../config/app.php' : [];
        $isDebug = !empty($appConfig['debug']);

        // Check if API / AJAX request
        $isApi = self::isJsonRequest();

        http_response_code($statusCode);

        if ($isApi) {
            header('Content-Type: application/json; charset=utf-8');
            $response = [
                'success' => false,
                'error'   => [
                    'code'    => $errorCode,
                    'message' => $userMessage,
                    'details' => $details
                ]
            ];

            if ($isDebug && $statusCode === 500) {
                $response['debug'] = [
                    'exception' => get_class($e),
                    'message'   => $e->getMessage(),
                    'file'      => $e->getFile(),
                    'line'      => $e->getLine()
                ];
            }

            echo json_encode($response, JSON_UNESCAPED_UNICODE);
            exit;
        }

        // Render Web View
        self::renderErrorView($statusCode, $userMessage, $errorCode, $details, $e, $isDebug);
        exit;
    }

    /**
     * Fatal shutdown handler
     */
    public static function handleShutdown(): void {
        $error = error_get_last();
        if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
            Logger::critical("Fatal Shutdown Error: " . $error['message'], [
                'file' => $error['file'],
                'line' => $error['line']
            ]);

            if (self::isJsonRequest()) {
                http_response_code(500);
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode([
                    'success' => false,
                    'error' => [
                        'code'    => 'FATAL_ERROR',
                        'message' => 'เกิดข้อผิดพลาดร้ายแรงในระบบ'
                    ]
                ], JSON_UNESCAPED_UNICODE);
                exit;
            }

            http_response_code(500);
            $viewFile = __DIR__ . '/../../views/errors/500.php';
            if (file_exists($viewFile)) {
                require $viewFile;
            } else {
                echo "<h1>500 Internal Server Error</h1><p>เกิดข้อผิดพลาดขึ้นในระบบ</p>";
            }
        }
    }

    private static function isJsonRequest(): bool {
        $uri = $_SERVER['REQUEST_URI'] ?? '';
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        $xRequested = $_SERVER['HTTP_X_REQUESTED_WITH'] ?? '';

        if (strpos($uri, '/api/') !== false) {
            return true;
        }
        if (strpos($accept, 'application/json') !== false) {
            return true;
        }
        if (strtolower($xRequested) === 'xmlhttprequest') {
            return true;
        }

        return false;
    }

    private static function renderErrorView(int $statusCode, string $message, string $errorCode, array $details, Throwable $e, bool $isDebug): void {
        $viewFile = __DIR__ . '/../../views/errors/' . $statusCode . '.php';
        if (!file_exists($viewFile)) {
            $viewFile = __DIR__ . '/../../views/errors/500.php';
        }

        // Variables made available to the error view
        $pageTitle = "Error {$statusCode} - CARGOO";
        $errorDetails = $details;
        $exception = $e;

        if (file_exists($viewFile)) {
            require $viewFile;
        } else {
            echo "<h1>Error {$statusCode}</h1><p>" . htmlspecialchars($message) . "</p>";
        }
    }
}
