<?php

declare(strict_types=1);

namespace App\Framework\Logging\Processors;

use App\Framework\Logging\LogProcessor;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\ExceptionContext;
use Throwable;

/**
 * Enriches log records with detailed exception information via ExceptionContext
 *
 * Nutzt das moderne ExceptionContext Value Object System für strukturierte
 * Exception-Daten statt Legacy-Array-Ansatz.
 *
 * Features:
 * - ExceptionContext mit StackFrame Value Objects
 * - Exception Hash für Pattern-Erkennung
 * - Exception Severity Categorization
 * - Short Stack Trace für Quick Overview
 */
final readonly class ExceptionEnrichmentProcessor implements LogProcessor
{
    public function __construct(
        private int $maxStackTraceDepth = 20,
        private bool $includeArgs = false
    ) {
    }

    public function processRecord(LogRecord $record): LogRecord
    {
        // Prüfe ob bereits ExceptionContext vorhanden
        if ($record->context->hasException()) {
            $exceptionContext = $record->context->getException();

            // Füge Enrichment-Daten zu Extra hinzu
            return $record->addExtras([
                'exception_hash' => $this->generateExceptionHash($exceptionContext),
                'exception_severity' => $this->categorizeExceptionSeverity($exceptionContext->class),
                'stack_trace_short' => $this->getShortStackTrace($exceptionContext),
                'chain_length' => $exceptionContext->getChainLength(),
            ]);
        }

        // Suche nach Exception in Context (für Legacy-Support)
        $exception = $this->findExceptionInContext($record->context->structured);
        if ($exception === null) {
            return $record;
        }

        // Erstelle ExceptionContext und füge zu Record hinzu
        $exceptionContext = ExceptionContext::fromThrowable($exception);

        return new LogRecord(
            level: $record->level,
            message: $record->message,
            channel: $record->channel,
            context: $record->context->withExceptionContext($exceptionContext),
            timestamp: $record->timestamp,
            extra: array_merge($record->extra, [
                'exception_hash' => $this->generateExceptionHash($exceptionContext),
                'exception_severity' => $this->categorizeExceptionSeverity($exceptionContext->class),
                'stack_trace_short' => $this->getShortStackTrace($exceptionContext),
                'chain_length' => $exceptionContext->getChainLength(),
            ])
        );
    }

    public function getPriority(): int
    {
        return 100; // High priority - run early
    }

    public function getName(): string
    {
        return 'exception_enrichment';
    }

    /**
     * Find exception in context data (Legacy-Support)
     */
    private function findExceptionInContext(array $context): ?Throwable
    {
        // Direct exception key
        if (isset($context['exception']) && $context['exception'] instanceof Throwable) {
            return $context['exception'];
        }

        // Search in nested context
        foreach ($context as $value) {
            if ($value instanceof Throwable) {
                return $value;
            }
        }

        return null;
    }

    /**
     * Get short stack trace from ExceptionContext (top 5 frames)
     */
    private function getShortStackTrace(ExceptionContext $exceptionContext): string
    {
        $frames = $exceptionContext->getTopFrames(5);
        $short = [];

        foreach ($frames as $frame) {
            $file = basename($frame->file);
            $line = $frame->line;
            $function = $frame->function ?? 'unknown';

            if ($frame->class !== null) {
                $class = $this->shortenClassName($frame->class);
                $function = "{$class}{$frame->type}{$function}";
            }

            $short[] = "{$file}:{$line} {$function}()";
        }

        return implode(' → ', $short);
    }

    /**
     * Generate unique hash for exception pattern from ExceptionContext
     */
    private function generateExceptionHash(ExceptionContext $exceptionContext): string
    {
        $signature = $exceptionContext->class .
                    $exceptionContext->file .
                    $exceptionContext->line .
                    $exceptionContext->message;

        return substr(md5($signature), 0, 8);
    }

    /**
     * Categorize exception severity by class name
     */
    private function categorizeExceptionSeverity(string $exceptionClass): string
    {
        return match(true) {
            str_contains($exceptionClass, 'Error') && !str_contains($exceptionClass, 'Exception') => 'fatal',
            str_contains($exceptionClass, 'ParseError') => 'fatal',
            str_contains($exceptionClass, 'TypeError') => 'high',
            str_contains($exceptionClass, 'InvalidArgumentException') => 'medium',
            str_contains($exceptionClass, 'LogicException') => 'high',
            str_contains($exceptionClass, 'RuntimeException') => 'medium',
            default => 'unknown'
        };
    }

    /**
     * Shorten class name for readability
     */
    private function shortenClassName(string $className): string
    {
        $parts = explode('\\', $className);

        if (count($parts) <= 2) {
            return $className;
        }

        // Keep first and last part: App\...\Controller
        return $parts[0] . '\\...\\' . end($parts);
    }
}
