<?php
declare(strict_types=1);

namespace App\Framework\Logging\Processors;

use App\Framework\Logging\LogProcessor;
use App\Framework\Logging\LogRecord;

/**
 * Verarbeitet Exceptions im Kontext und fügt detaillierte Informationen hinzu.
 */
final class ExceptionProcessor implements LogProcessor
{
    /**
     * @var int Priorität des Processors (15 = früh, nach Interpolation)
     */
    private const int PRIORITY = 15;

    /**
     * @var bool Ob Stack-Traces hinzugefügt werden sollen
     */
    private bool $includeStackTraces;

    /**
     * @var int Maximale Tiefe für Stack-Traces
     */
    private int $traceDepth;

    /**
     * Erstellt einen neuen ExceptionProcessor
     */
    public function __construct(
        bool $includeStackTraces = true,
        int $traceDepth = 10
    ) {
        $this->includeStackTraces = $includeStackTraces;
        $this->traceDepth = $traceDepth;
    }

    /**
     * Verarbeitet einen Log-Record und extrahiert Exception-Informationen
     */
    public function processRecord(LogRecord $record): LogRecord
    {
        $context = $record->getContext();

        // Prüfen, ob eine Exception im Kontext vorhanden ist
        if (isset($context['exception']) && $context['exception'] instanceof \Throwable) {
            $exception = $context['exception'];
            $exceptionData = $this->formatException($exception);

            // Exception-Daten zu Extra-Daten hinzufügen
            $record->addExtra('exception', $exceptionData);
        }

        return $record;
    }

    /**
     * Formatiert eine Exception für das Logging
     */
    private function formatException(\Throwable $exception): array
    {
        $result = [
            'class' => get_class($exception),
            'message' => $exception->getMessage(),
            'code' => $exception->getCode(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
        ];

        // Stack-Trace hinzufügen, wenn aktiviert
        if ($this->includeStackTraces) {
            $result['trace'] = $this->formatTrace($exception);
        }

        // Bei verschachtelten Exceptions auch die vorherige Exception formatieren
        if ($exception->getPrevious()) {
            $result['previous'] = $this->formatException($exception->getPrevious());
        }

        return $result;
    }

    /**
     * Formatiert den Stack-Trace einer Exception
     */
    private function formatTrace(\Throwable $exception): array
    {
        $trace = $exception->getTrace();
        $result = [];

        // Tiefe begrenzen
        $trace = array_slice($trace, 0, $this->traceDepth);

        foreach ($trace as $frame) {
            $entry = [
                'file' => $frame['file'] ?? 'unknown',
                'line' => $frame['line'] ?? 0,
            ];

            if (isset($frame['function'])) {
                $function = '';
                if (isset($frame['class'])) {
                    $function .= $frame['class'] . $frame['type'];
                }
                $function .= $frame['function'];
                $entry['function'] = $function;
            }

            $result[] = $entry;
        }

        return $result;
    }

    /**
     * Gibt die Priorität des Processors zurück
     */
    public function getPriority(): int
    {
        return self::PRIORITY;
    }

    /**
     * Gibt einen eindeutigen Namen für den Processor zurück
     */
    public function getName(): string
    {
        return 'exception';
    }
}
