<?php

declare(strict_types=1);

namespace App\Framework\ErrorHandling;

use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\DI\Container;
use App\Framework\ErrorAggregation\ErrorAggregatorInterface;
use App\Framework\ErrorHandling\View\ApiErrorRenderer;
use App\Framework\ErrorHandling\View\ErrorResponseFactory;
use App\Framework\ErrorHandling\View\ErrorTemplateRenderer;
use App\Framework\ErrorReporting\ErrorReporterInterface;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\RequestContext;
use App\Framework\Exception\SecurityException;
use App\Framework\Exception\SecurityLogLevel;
use App\Framework\Exception\SystemContext;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\RequestIdGenerator;
use App\Framework\Http\Response;
use App\Framework\Http\ResponseEmitter;
use App\Framework\Http\Responses\RedirectResponse;
use App\Framework\Logging\Logger;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Validation\Exceptions\ValidationException;
use App\Framework\Validation\ValidationFormHandler;
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,
        private ErrorAggregatorInterface $errorAggregator,
        private ErrorReporterInterface   $errorReporter,
        ?Logger                          $logger = null,
        ?bool                            $isDebugMode = null,
        ?SecurityEventHandler            $securityHandler = null
    ) {
        $this->isDebugMode = $isDebugMode ?? $this->getDebugModeFromEnvironment();
        $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): Response
    {
        // Handle ValidationException with form-specific logic
        if ($e instanceof ValidationException) {
            return $this->handleValidationException($e, $context);
        }

        $errorHandlerContext = $this->createErrorHandlerContext($e, $context);

        // Standard Error Logging mit ErrorHandlerContext
        $this->logger->logErrorHandlerContext($errorHandlerContext);

        // Error Aggregation - automatische Pattern-Erkennung und Alerting
        $this->errorAggregator->processError($errorHandlerContext);

        // Error Reporting - zentrale Fehlersammlung mit Context Enrichment
        $this->errorReporter->reportThrowable($e, 'error', $errorHandlerContext->toArray());

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

        $responseFactory = $this->createResponseFactory();

        $isApiRequest = $responseFactory->isApiRequest();

        return $responseFactory->createResponseFromHandlerContext(
            $errorHandlerContext,
            $this->isDebugMode,
            $isApiRequest
        );
    }

    private function handleValidationException(ValidationException $exception, ?MiddlewareContext $context): Response
    {
        // Try to get ValidationFormHandler from container
        if ($this->container->has(ValidationFormHandler::class)) {
            $formHandler = $this->container->get(ValidationFormHandler::class);

            return $formHandler->handle($exception, $context);
        }

        // Log missing ValidationFormHandler using structured logging
        $errorContext = $this->createErrorHandlerContext(
            new \RuntimeException('ValidationFormHandler not found in container'),
            $context
        );
        $this->logger->logErrorHandlerContext($errorContext);
        // Fallback: create basic redirect response
        $refererUrl = $context?->request?->server?->getRefererUri() ?? '/';

        return new RedirectResponse($refererUrl);
    }

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

        // Logge den Fehler mit ErrorHandlerContext
        $this->logger->logErrorHandlerContext($errorHandlerContext);

        // Error Aggregation - automatische Pattern-Erkennung und Alerting
        $this->errorAggregator->processError($errorHandlerContext);

        // Error Reporting - zentrale Fehlersammlung mit Context Enrichment
        $this->errorReporter->reportThrowable($e, 'error', $errorHandlerContext->toArray());

        // 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 ErrorHandlerContext
        $response = $responseFactory->createResponseFromHandlerContext($errorHandlerContext, $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 createErrorHandlerContext(Throwable $exception, ?MiddlewareContext $context = null): ErrorHandlerContext
    {
        // ExceptionContext aus der Exception extrahieren (falls FrameworkException)
        $exceptionContext = ExceptionContext::empty();
        if ($exception instanceof FrameworkException) {
            $exceptionContext = $exception->getContext();


            // Für DatabaseException: Verwende die previous exception für File/Line
            if ($exception instanceof \App\Framework\Database\Exception\DatabaseException && $exception->getPrevious()) {
                $previous = $exception->getPrevious();


                $exceptionContext = $exceptionContext->withData([
                    'original_exception' => $previous,
                    'exception_file' => $previous->getFile(),
                    'exception_line' => $previous->getLine(),
                    'exception_message' => $exception->getMessage(), // Keep DatabaseException message
                ]);

            } else {
                // Ensure framework exceptions also have the original exception for stack traces
                if (! isset($exceptionContext->data['original_exception'])) {
                    $exceptionContext = $exceptionContext->withData([
                        'original_exception' => $exception,
                        'exception_file' => $exception->getFile(),
                        'exception_line' => $exception->getLine(),
                    ]);
                }
            }
        } else {
            // For non-framework exceptions, add the actual exception message and the original exception
            $exceptionContext = $exceptionContext->withData([
                'exception_message' => $exception->getMessage(),
                'exception_file' => $exception->getFile(),
                'exception_line' => $exception->getLine(),
                'original_exception' => $exception, // Preserve original exception for stack trace
            ]);
        }

        // RequestContext aus MiddlewareContext oder Globals erstellen
        $requestContext = $this->createRequestContext($context);

        // SystemContext mit aktuellen System-Metriken erstellen
        $memoryMonitor = $this->container->has(MemoryMonitor::class)
            ? $this->container->get(MemoryMonitor::class)
            : null;
        $systemContext = SystemContext::current($memoryMonitor);

        // Metadata für verschiedene Exception-Typen
        $metadata = $this->createExceptionMetadata($exception);

        return ErrorHandlerContext::create(
            $exceptionContext,
            $requestContext,
            $systemContext,
            $metadata
        );
    }

    private function createRequestContext(?MiddlewareContext $context): RequestContext
    {
        if ($context?->request) {
            $request = $context->request;

            return RequestContext::create(
                clientIp: (string) $request->server->getClientIp(),
                userAgent: $request->server->getUserAgent(),
                requestMethod: $request->method->value,
                requestUri: (string) $request->getUri(),
                hostIp: $request->server->get('SERVER_ADDR', 'unknown'),
                hostname: $request->server->getHttpHost() ?: 'localhost',
                protocol: $request->server->getProtocol()->value,
                port: (string) $request->server->getServerPort(),
                requestId: $request->id
            );
        }

        return RequestContext::fromGlobals();
    }

    /**
     * @return array<string, mixed>
     */
    private function createExceptionMetadata(Throwable $exception): array
    {
        $metadata = [
            'exception_class' => get_class($exception),
            'error_level' => $this->determineErrorSeverity($exception)->name,
        ];

        // Enhanced: Add ErrorCode metadata if FrameworkException
        if ($exception instanceof FrameworkException) {
            $errorCode = $exception->getErrorCode();
            if ($errorCode !== null) {
                $metadata['error_code'] = $errorCode->getValue();
                $metadata['error_category'] = $errorCode->getCategory();
                $metadata['error_severity'] = $errorCode->getSeverity()->value;
                $metadata['is_recoverable'] = $errorCode->isRecoverable();

                // Add recovery hint for debug mode or API responses
                if ($this->isDebugMode) {
                    $metadata['recovery_hint'] = $errorCode->getRecoveryHint();
                }

                // Add Retry-After header if applicable
                $retryAfter = $errorCode->getRetryAfterSeconds();
                if ($retryAfter !== null) {
                    $metadata['additional_headers'] = array_merge(
                        $metadata['additional_headers'] ?? [],
                        ['Retry-After' => (string) $retryAfter]
                    );
                }
            }
        }

        // HTTP-Status-Code: ErrorCode-based first, then fallback to exception-type mapping
        $metadata['http_status'] = $this->determineHttpStatus($exception);

        // Zusätzliche Header für spezielle Exceptions
        if ($exception instanceof \App\Framework\Exception\Http\RateLimitExceededException) {
            $metadata['additional_headers'] = array_merge(
                $metadata['additional_headers'] ?? [],
                $exception->getRateLimitHeaders()
            );
        }

        if ($exception instanceof \App\Framework\Exception\Http\InvalidContentTypeException) {
            $metadata['additional_headers'] = array_merge(
                $metadata['additional_headers'] ?? [],
                $exception->getResponseHeaders()
            );
        }

        return $metadata;
    }

    /**
     * Determine HTTP status code with ErrorCode-based prioritization
     */
    private function determineHttpStatus(Throwable $exception): int
    {
        // Priority 1: ErrorCode-based status mapping for FrameworkException
        if ($exception instanceof FrameworkException) {
            $errorCode = $exception->getErrorCode();
            if ($errorCode !== null) {
                $category = $errorCode->getCategory();

                return match ($category) {
                    'AUTH' => 401, // Authentication errors
                    'AUTHZ' => 403, // Authorization errors
                    'VAL' => 400,  // Validation errors
                    'HTTP' => $this->mapHttpCategoryToStatus($exception),
                    'DB', 'QUEUE', 'CACHE', 'FILE' => 500, // Infrastructure errors
                    default => 500,
                };
            }
        }

        // Priority 2: Legacy exception-type mapping
        return match (true) {
            $exception instanceof \App\Framework\Exception\Authentication\InvalidCredentialsException,
            $exception instanceof \App\Framework\Exception\Authentication\TokenExpiredException,
            $exception instanceof \App\Framework\Exception\Authentication\SessionTimeoutException => 401,

            $exception instanceof \App\Framework\Exception\Authentication\AccountLockedException => 423,
            $exception instanceof \App\Framework\Exception\Authentication\InsufficientPrivilegesException => 403,

            $exception instanceof \App\Framework\Exception\Http\RouteNotFoundException => 404,
            $exception instanceof \App\Framework\Exception\Http\MalformedJsonException,
            $exception instanceof \App\Framework\Exception\Http\InvalidContentTypeException,
            $exception instanceof \App\Framework\Exception\Security\SqlInjectionAttemptException,
            $exception instanceof \App\Framework\Exception\Security\XssAttemptException,
            $exception instanceof \App\Framework\Exception\Security\PathTraversalAttemptException => 400,

            $exception instanceof \App\Framework\Exception\Http\OversizedRequestException => 413,
            $exception instanceof \App\Framework\Exception\Http\RateLimitExceededException => 429,

            default => 500,
        };
    }

    /**
     * Map HTTP category ErrorCodes to specific status codes
     */
    private function mapHttpCategoryToStatus(FrameworkException $exception): int
    {
        $errorCode = $exception->getErrorCode();

        // Use numeric code for fine-grained HTTP status mapping
        return match ($errorCode->getNumericCode()) {
            404 => 404, // HTTP_NOT_FOUND
            429 => 429, // HTTP_RATE_LIMIT_EXCEEDED
            413 => 413, // HTTP_OVERSIZED_REQUEST
            415 => 415, // HTTP_INVALID_CONTENT_TYPE
            default => 500,
        };
    }

    private function determineErrorSeverity(Throwable $exception): ErrorSeverity
    {
        // Priority 1: Use ErrorCode.getSeverity() for FrameworkException
        if ($exception instanceof FrameworkException) {
            $errorCode = $exception->getErrorCode();
            if ($errorCode !== null) {
                return $errorCode->getSeverity();
            }
        }

        // Priority 2: Legacy exception-type mapping
        return match(true) {
            $exception instanceof \Error => ErrorSeverity::CRITICAL,
            $exception instanceof \ErrorException => $this->determineErrorExceptionSeverity($exception),
            $exception instanceof SecurityException => $this->determineSecurityErrorSeverity($exception),
            $exception instanceof \RuntimeException => ErrorSeverity::ERROR,
            $exception instanceof \LogicException => ErrorSeverity::WARNING,
            default => ErrorSeverity::ERROR,
        };
    }

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

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

    /**
     * Erstellt eine ErrorResponseFactory mit Renderern
     * Versucht den TemplateRenderer aus dem Container zu laden, falls vorhanden
     */
    private function getDebugModeFromEnvironment(): bool
    {
        try {
            if ($this->container->has(Environment::class)) {
                $environment = $this->container->get(Environment::class);

                return $environment->getBool(EnvKey::APP_DEBUG, false);
            }
        } catch (Throwable $e) {
            // Sicherer Fallback für Production
            return false;
        }

        return false;
    }

    private function createResponseFactory(): ErrorResponseFactory
    {
        $templateRenderer = null;

        // Versuche TemplateRenderer aus Container zu laden
        if ($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);
    }
}
