<?php

declare(strict_types=1);

namespace App\Framework\ErrorHandling;

use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\ValueObjects\LogContext;

/**
 * Logger für Fehler und Exceptions - nutzt Framework Logger
 */
final readonly class ErrorLogger
{
    public function __construct(
        private ?Logger $logger = null
    ) {
    }

    /**
     * Loggt einen Fehler basierend auf dem ErrorContext (Legacy)
     */
    public function logError(ErrorContext $context): void
    {
        $exception = $context->exception;
        $message = sprintf(
            '[%s] %s: %s in %s:%d',
            $context->requestId,
            get_class($exception),
            $exception->getMessage(),
            $exception->getFile(),
            $exception->getLine()
        );

        $contextData = [
            'exception' => $exception,
            'trace' => $exception->getTraceAsString(),
            'requestId' => $context->requestId,
            'additionalData' => $context->additionalData,
        ];

        if ($this->logger !== null) {
            $logLevel = $this->mapErrorSeverityToLogLevel($context->level);
            $this->logger->log($logLevel, $message, $contextData);
        } else {
            // Fallback auf error_log
            $this->logToErrorLog($context);
        }
    }

    /**
     * Loggt einen Fehler basierend auf dem ErrorHandlerContext
     */
    public function logErrorHandlerContext(ErrorHandlerContext $context): void
    {
        // Structured Logging mit allen Context-Informationen
        $logData = $context->forLogging();

        // Security Event Logging falls Security-Event
        if ($context->exception->metadata['security_event'] ?? false) {
            $this->logSecurityEvent($context);
        }

        // Standard Error Logging
        $message = sprintf(
            '[%s] %s: %s',
            $context->request->requestId?->toString() ?? 'NO-REQUEST-ID',
            $context->exception->component ?? 'Application',
            $this->extractExceptionMessage($context)
        );


        if ($this->logger !== null) {
            $logLevel = $this->determineLogLevel($context);

            // Erstelle LogContext mit strukturierten Daten
            $logContext = LogContext::withData($logData)
                ->addTags('error_handler', 'framework');

            // Füge Metadaten-Tags hinzu
            if ($context->exception->metadata['security_event'] ?? false) {
                $logContext = $logContext->addTags('security');
            }

            $this->logger->log($logLevel, $message, $logContext);
        } else {
            // Fallback auf error_log
            $this->logHandlerContextToErrorLog($context);
        }
    }

    /**
     * Loggt Security Events im OWASP-Format mit strukturiertem Logging
     */
    private function logSecurityEvent(ErrorHandlerContext $context): void
    {
        $securityLog = $context->toSecurityEventFormat($_ENV['APP_NAME'] ?? 'app');

        if ($this->logger !== null) {
            // Verwende LogContext für strukturiertes Security Event Logging
            $logContext = LogContext::withData($securityLog)
                ->addTags('security_event', 'owasp');

            $this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext);
        } else {
            // Fallback nur als letzte Option
            error_log('SECURITY_EVENT: ' . json_encode($securityLog, JSON_UNESCAPED_SLASHES));
        }
    }

    /**
     * Extrahiert Exception-Message aus ErrorHandlerContext
     */
    private function extractExceptionMessage(ErrorHandlerContext $context): string
    {
        // Versuche Exception aus context.exception.data zu extrahieren
        $exceptionData = $context->exception->data;

        if (isset($exceptionData['exception_message'])) {
            return $exceptionData['exception_message'];
        }


        // Fallback: Operation und Component verwenden
        $operation = $context->exception->operation ?? 'unknown_operation';
        $component = $context->exception->component ?? 'unknown_component';

        return "Error in {$component} during {$operation}";
    }

    /**
     * Bestimmt Log-Level aus ErrorHandlerContext
     */
    private function determineLogLevel(ErrorHandlerContext $context): LogLevel
    {
        // Security Events haben höhere Priorität
        if ($context->exception->metadata['security_event'] ?? false) {
            $securityLevel = $context->exception->metadata['security_level'] ?? 'ERROR';

            return match ($securityLevel) {
                'DEBUG' => LogLevel::DEBUG,
                'INFO' => LogLevel::INFO,
                'WARN' => LogLevel::WARNING,
                'ERROR' => LogLevel::ERROR,
                'FATAL' => LogLevel::CRITICAL,
                default => LogLevel::ERROR,
            };
        }

        // Standard Log-Level aus Metadata
        $errorLevel = $context->exception->metadata['error_level'] ?? 'ERROR';

        return match ($errorLevel) {
            'CRITICAL' => LogLevel::CRITICAL,
            'ERROR' => LogLevel::ERROR,
            'WARNING' => LogLevel::WARNING,
            'NOTICE' => LogLevel::NOTICE,
            'INFO' => LogLevel::INFO,
            'DEBUG' => LogLevel::DEBUG,
            default => LogLevel::ERROR,
        };
    }

    /**
     * Fallback-Methode für ErrorHandlerContext ohne Framework Logger
     */
    private function logHandlerContextToErrorLog(ErrorHandlerContext $context): void
    {
        $message = sprintf(
            '[%s] [%s] %s: %s',
            date('Y-m-d H:i:s'),
            $context->exception->metadata['error_level'] ?? 'ERROR',
            $context->exception->component ?? 'Application',
            $this->extractExceptionMessage($context)
        );

        error_log($message);

        // Security Events auch separat loggen
        if ($context->exception->metadata['security_event'] ?? false) {
            $securityLog = $context->toSecurityEventJson($_ENV['APP_NAME'] ?? 'app');
            error_log('SECURITY_EVENT: ' . $securityLog);
        }
    }

    /**
     * Fallback-Methode für Logging ohne Framework Logger (Legacy)
     */
    private function logToErrorLog(ErrorContext $context): void
    {
        $exception = $context->exception;
        $message = sprintf(
            '[%s] [%s] %s: %s in %s:%d',
            date('Y-m-d H:i:s'),
            $context->level->name,
            get_class($exception),
            $exception->getMessage(),
            $exception->getFile(),
            $exception->getLine()
        );

        error_log($message);

        // Bei kritischen Fehlern auch den Stacktrace loggen
        if ($context->level === ErrorSeverity::CRITICAL) {
            error_log("Stack trace: \n" . $exception->getTraceAsString());
        }
    }

    /**
     * Mappt ErrorSeverity auf Framework LogLevel
     */
    private function mapErrorSeverityToLogLevel(ErrorSeverity $severity): LogLevel
    {
        return match($severity) {
            ErrorSeverity::CRITICAL => LogLevel::CRITICAL,
            ErrorSeverity::ERROR => LogLevel::ERROR,
            ErrorSeverity::WARNING => LogLevel::WARNING,
            ErrorSeverity::NOTICE => LogLevel::NOTICE,
            ErrorSeverity::INFO => LogLevel::INFO,
            ErrorSeverity::DEBUG => LogLevel::DEBUG,
        };
    }
}
