<?php

namespace App\Framework\ErrorHandling;

use App\Framework\DI\Container;
use App\Framework\ErrorHandling\View\ApiErrorRenderer;
use App\Framework\ErrorHandling\View\ErrorResponseFactory;
use App\Framework\ErrorHandling\View\ErrorTemplateRenderer;
use App\Framework\Exception\SecurityException;
use App\Framework\Exception\SecurityLogLevel;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\RequestIdGenerator;
use App\Framework\Http\ResponseEmitter;
use App\Framework\Logging\Logger;
use App\Framework\View\TemplateRenderer;
use Throwable;

final readonly class ErrorHandler
{
    private ErrorLogger $logger;
    private SecurityEventHandler $securityHandler;
    private bool $isDebugMode;

    public function __construct(
        private ResponseEmitter    $emitter,
        private Container          $container,
        private RequestIdGenerator $requestIdGenerator,
        ?Logger                    $logger = null,
        ?bool                      $isDebugMode = null,
        ?SecurityEventHandler      $securityHandler = null
    ){
        $this->isDebugMode = $isDebugMode ?? (bool)($_ENV['APP_DEBUG'] ?? false);
        $this->logger = new ErrorLogger($logger);
        $this->securityHandler = $securityHandler ?? SecurityEventHandler::createDefault($logger);
    }

    public function register(): void
    {
        set_exception_handler($this->handleException(...));
        set_error_handler($this->handleError(...));;
        register_shutdown_function($this->handleShutdown(...));;
    }

    public function createHttpResponse(\Throwable $e, ?MiddlewareContext $context = null): HttpResponse
    {
        $errorContext = $this->createErrorContext($e, $context);

        // Standard Error Logging
        $this->logger->logError($errorContext);

        // Security Event Logging delegieren
        $this->securityHandler->handleIfSecurityException($e, $context);

        $responseFactory = $this->createResponseFactory();

        $isApiRequest = $responseFactory->isApiRequest();

        return $responseFactory->createResponse(
            $errorContext,
            $this->isDebugMode,
            $isApiRequest
        );
    }

    public function handleException(Throwable $e): never
    {
        $context = $this->createErrorContext($e);

        // Logge den Fehler
        $this->logger->logError($context);

        // Security Event Logging delegieren
        $this->securityHandler->handleIfSecurityException($e);

        // Erstelle ResponseFactory mit dem TemplateRenderer
        $responseFactory = $this->createResponseFactory();

        // Bestimme ob es ein API-Request ist
        $isApiRequest = $responseFactory->isApiRequest();

        // Erstelle eine Response basierend auf dem Kontext
        $response = $responseFactory->createResponse($context, $this->isDebugMode, $isApiRequest);

        // Sende die Fehlermeldung
        $this->emitter->emit($response);
        exit(1);
    }

    public function handleError(int $errno, string $errstr, string $errfile, int $errline): void
    {
        $exception = new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        $this->handleException($exception);
    }

    public function handleShutdown(): void
    {
        $error = error_get_last();
        if ($error && $this->isFatalError($error['type'])) {
            $exception = new \ErrorException(
                $error['message'],
                0,
                $error['type'],
                $error['file'],
                $error['line']
            );
            $this->handleException($exception);
        }
    }

    private function isFatalError(int $errno): bool
    {
        return in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR], true);
    }

    private function createErrorContext(Throwable $exception, ?MiddlewareContext $context = null):ErrorContext
    {
        $level = $this->determineErrorLevel($exception);

        $requestId = $context->requestId ?? $this->requestIdGenerator->generate();

        return new ErrorContext(
            exception: $exception,
            level: $level,
            requestId: $requestId,
            additionalData: [
                'timestamp' => date('c'),
                'memory_usage' => memory_get_peak_usage(true),
            ]
        );
    }

    private function determineErrorLevel(Throwable $exception):ErrorLevel
    {
        return match(true) {
            $exception instanceof \Error => ErrorLevel::CRITICAL,
            $exception instanceof \ErrorException => $this->determineErrorExceptionLevel($exception),
            $exception instanceof SecurityException => $this->determineSecurityErrorLevel($exception),
            $exception instanceof \RuntimeException => ErrorLevel::ERROR,
            $exception instanceof \LogicException => ErrorLevel::WARNING,
            default => ErrorLevel::ERROR,
        };
    }

    /**
     * Bestimmt den Error-Level für SecurityExceptions basierend auf dem Security-Level
     */
    private function determineSecurityErrorLevel(SecurityException $exception): ErrorLevel
    {
        return match($exception->getSecurityLevel()) {
            SecurityLogLevel::DEBUG => ErrorLevel::DEBUG,
            SecurityLogLevel::INFO => ErrorLevel::INFO,
            SecurityLogLevel::WARN => ErrorLevel::WARNING,
            SecurityLogLevel::ERROR => ErrorLevel::ERROR,
            SecurityLogLevel::FATAL => ErrorLevel::CRITICAL,
        };
    }


    /**
     * Bestimmt den Fehler-Level basierend auf dem ErrorException-Severity-Level
     */
    private function determineErrorExceptionLevel(\ErrorException $exception): ErrorLevel
    {
        return match($exception->getSeverity()) {
            E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR => ErrorLevel::CRITICAL,
            E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING => ErrorLevel::WARNING,
            E_NOTICE, E_USER_NOTICE => ErrorLevel::NOTICE,
            E_DEPRECATED, E_USER_DEPRECATED => ErrorLevel::INFO,
            E_STRICT => ErrorLevel::DEBUG,
            default => ErrorLevel::ERROR,
        };
    }

    /**
     * Erstellt eine ErrorResponseFactory mit Renderern
     * Versucht den TemplateRenderer aus dem Container zu laden, falls vorhanden
     */
    private function createResponseFactory(): ErrorResponseFactory
    {
        $templateRenderer = null;

        // Versuche TemplateRenderer aus Container zu laden
        if ($this->container && $this->container->has(TemplateRenderer::class)) {
            try {
                $templateRenderer = $this->container->get(TemplateRenderer::class);
            } catch (Throwable $e) {
                // Falls ein Fehler beim Laden auftritt, verwende Fallback
                $templateRenderer = null;
            }
        }

        $htmlRenderer = $templateRenderer ?
            new ErrorTemplateRenderer($templateRenderer) :
            new ErrorTemplateRenderer(new DummyTemplateRenderer());

        $apiRenderer = new ApiErrorRenderer();

        return new ErrorResponseFactory($htmlRenderer, $apiRenderer);
    }
}
