<?php

declare(strict_types=1);

namespace App\Framework\Logging;

use App\Framework\Attributes\Singleton;
use App\Framework\Logging\ValueObjects\LogContext;

/**
 * Interceptor für PHP's native error_log() - Elegant und transparent
 */
#[Singleton]
final class PhpErrorLogInterceptor
{
    private $interceptStream = null;

    private ?string $originalErrorLog = null;

    private bool $isInstalled = false;

    public function __construct(
        private Logger $logger
    ) {
    }

    /**
     * Installiert den Interceptor - fängt alle error_log() Aufrufe ab
     */
    public function install(): void
    {
        if ($this->isInstalled) {
            return;
        }

        // Aktuelles error_log Setting speichern
        $this->originalErrorLog = ini_get('error_log') ?: null;

        // Temporären Stream für Interception erstellen
        $this->interceptStream = fopen('php://temp', 'r+');
        if ($this->interceptStream === false) {
            throw new \RuntimeException('Could not create intercept stream');
        }

        // PHP error_log auf unseren Stream umleiten
        $streamPath = stream_get_meta_data($this->interceptStream)['uri'];
        ini_set('error_log', $streamPath);

        // Stream-Monitoring in separatem Process starten
        $this->startStreamMonitoring();

        $this->isInstalled = true;
    }

    /**
     * Deinstalliert den Interceptor und stellt original error_log wieder her
     */
    public function uninstall(): void
    {
        if (! $this->isInstalled) {
            return;
        }

        // Original error_log Setting wiederherstellen
        if ($this->originalErrorLog !== null) {
            ini_set('error_log', $this->originalErrorLog);
        } else {
            ini_restore('error_log');
        }

        // Stream schließen
        if ($this->interceptStream !== null) {
            fclose($this->interceptStream);
            $this->interceptStream = null;
        }

        $this->isInstalled = false;
    }

    /**
     * Startet das Stream-Monitoring für error_log Interception
     */
    private function startStreamMonitoring(): void
    {
        if ($this->interceptStream === null) {
            return;
        }

        // Nicht-blockierender Stream
        stream_set_blocking($this->interceptStream, false);

        // Register shutdown function um letzten Content zu lesen
        register_shutdown_function(function () {
            $this->readAndForwardLogs();
        });

        // Für CLI: Monitoring-Loop in Background
        if (php_sapi_name() === 'cli') {
            $this->startCliMonitoring();
        } else {
            // Für Web: Output Buffer Hook
            $this->startWebMonitoring();
        }
    }

    /**
     * CLI Monitoring - Background Stream Reading
     */
    private function startCliMonitoring(): void
    {
        // Registriere Tick-Handler für periodisches Lesen
        declare(ticks=1);
        register_tick_function(function () {
            $this->readAndForwardLogs();
        });
    }

    /**
     * Web Monitoring - Output Buffer Hook
     */
    private function startWebMonitoring(): void
    {
        // Output buffer callback um am Ende der Request zu lesen
        $callback = function (string $buffer): string {
            $this->readAndForwardLogs();

            return $buffer;
        };

        \App\Framework\OutputBuffer\OutputBuffer::start(
            \App\Framework\OutputBuffer\OutputBufferConfig::forWebMonitoring($callback)
        );
    }

    /**
     * Liest Logs aus dem Stream und leitet sie an Framework Logger weiter
     */
    private function readAndForwardLogs(): void
    {
        if ($this->interceptStream === null) {
            return;
        }

        // Stream position zurücksetzen
        rewind($this->interceptStream);

        // Alles aus dem Stream lesen
        $content = stream_get_contents($this->interceptStream);
        if ($content === false || $content === '') {
            return;
        }

        // Stream für nächste Writes leeren
        ftruncate($this->interceptStream, 0);
        rewind($this->interceptStream);

        // Content in einzelne Log-Zeilen aufteilen
        $lines = array_filter(explode("\n", trim($content)));

        foreach ($lines as $line) {
            $this->forwardLogLine(trim($line));
        }
    }

    /**
     * Leitet eine einzelne Log-Zeile an Framework Logger weiter
     */
    private function forwardLogLine(string $line): void
    {
        if (empty($line)) {
            return;
        }

        // Parse PHP error_log Format: [timestamp] message
        $context = $this->parseErrorLogLine($line);

        // Bestimme Log Level basierend auf Content
        $level = $this->determineLogLevel($line);

        // An Framework Logger weiterleiten
        $this->logger->error->log($level, $line, $context);

        // Optional: Auch an original error_log weiterleiten
        if ($this->originalErrorLog !== null && $this->originalErrorLog !== '') {
            $this->forwardToOriginalErrorLog($line);
        }
    }

    /**
     * Parst eine error_log Zeile und extrahiert Context
     */
    private function parseErrorLogLine(string $line): LogContext
    {
        $caller = $this->extractCaller();

        $context = LogContext::empty()
            ->withData([
                'source' => 'php_error_log',
                'original_line' => $line,
                'caller' => $caller,
                'intercepted_at' => date('Y-m-d H:i:s'),
            ]);

        // Parse PHP Error Format wenn möglich
        if (preg_match('/^\[([^\]]+)\]\s+(.+)$/', $line, $matches)) {
            $context = $context->withData([
                'original_timestamp' => $matches[1],
                'parsed_message' => $matches[2],
            ]);
        }

        return $context;
    }

    /**
     * Bestimmt das passende Log Level für eine Nachricht
     */
    private function determineLogLevel(string $line): LogLevel
    {
        $lowerLine = strtolower($line);

        return match (true) {
            str_contains($lowerLine, 'fatal') || str_contains($lowerLine, 'emergency') => LogLevel::EMERGENCY,
            str_contains($lowerLine, 'critical') || str_contains($lowerLine, 'alert') => LogLevel::CRITICAL,
            str_contains($lowerLine, 'error') => LogLevel::ERROR,
            str_contains($lowerLine, 'warning') || str_contains($lowerLine, 'warn') => LogLevel::WARNING,
            str_contains($lowerLine, 'notice') => LogLevel::NOTICE,
            str_contains($lowerLine, 'info') => LogLevel::INFO,
            str_contains($lowerLine, 'debug') => LogLevel::DEBUG,
            default => LogLevel::ERROR, // Default für error_log
        };
    }

    /**
     * Extrahiert Aufrufer-Informationen aus Backtrace
     */
    private function extractCaller(): array
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

        // Suche nach dem ersten Aufruf außerhalb des Interceptors
        foreach ($trace as $frame) {
            if (
                isset($frame['file']) &&
                ! str_contains($frame['file'], 'PhpErrorLogInterceptor.php') &&
                ($frame['function'] ?? '') !== 'error_log'
            ) {
                return [
                    'file' => $frame['file'],
                    'line' => $frame['line'] ?? 0,
                    'function' => $frame['function'] ?? 'unknown',
                    'class' => $frame['class'] ?? null,
                ];
            }
        }

        return ['file' => 'unknown', 'line' => 0, 'function' => 'unknown', 'class' => null];
    }

    /**
     * Leitet Log an originales error_log weiter (optional)
     */
    private function forwardToOriginalErrorLog(string $line): void
    {
        if ($this->originalErrorLog === null) {
            return;
        }

        // Temporär original error_log wiederherstellen
        $current = ini_get('error_log');
        ini_set('error_log', $this->originalErrorLog);

        // An original error_log senden
        \error_log($line);

        // Wieder auf unseren Stream umstellen
        ini_set('error_log', $current);
    }

    /**
     * Prüft ob der Interceptor installiert ist
     */
    public function isInstalled(): bool
    {
        return $this->isInstalled;
    }

    /**
     * Destruktor - stellt sicher dass alles sauber aufgeräumt wird
     */
    public function __destruct()
    {
        $this->readAndForwardLogs(); // Letzte Logs lesen
    }
}
