<?php

declare(strict_types=1);

namespace App\Framework\Logging\Handlers;

use App\Framework\Core\PathProvider;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\LogConfig;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;

/**
 * Handler für automatisches Routing von Log-Nachrichten in verschiedene Dateien
 *
 * Schreibt Logs basierend auf dem Channel in verschiedene Dateien.
 * Verwendet LogConfig für die Pfad-Auflösung und PathProvider für die Pfad-Normalisierung.
 */
final class MultiFileHandler implements LogHandler
{
    /**
     * @var array<string, resource> Cache für geöffnete Datei-Handles
     */
    private array $fileHandles = [];

    public function __construct(
        private readonly mixed $logConfig,
        private readonly mixed $pathProvider,
        private readonly LogLevel $minLevel = LogLevel::DEBUG,
        private readonly string $outputFormat = '[{timestamp}] [{level_name}] [{channel}] {message}',
        private readonly int $fileMode = 0644
    ) {
    }

    /**
     * Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
     */
    public function isHandling(LogRecord $record): bool
    {
        return $record->getLevel()->isHigherThan($this->minLevel) || $record->getLevel()->isEqual($this->minLevel);
    }

    /**
     * Verarbeitet den Log-Eintrag und schreibt ihn in die entsprechende Datei
     */
    public function handle(LogRecord $record): void
    {
        if (! $this->isHandling($record)) {
            return;
        }

        // Channel aus Record extrahieren, Fallback zu 'app'
        $channel = $record->getChannel() ?? LogChannel::APP->value;

        // Entsprechende Log-Datei ermitteln
        $logFile = $this->getLogFileForChannel($channel);

        // Log-Nachricht formatieren
        $formattedMessage = $this->formatMessage($record);

        // In Datei schreiben
        $this->writeToFile($logFile, $formattedMessage);
    }

    /**
     * Ermittelt die Log-Datei für einen Channel
     */
    private function getLogFileForChannel(string $channel): string
    {
        // Channel zu LogConfig-Key mappen
        $logPathKey = match ($channel) {
            LogChannel::SECURITY->value => 'security',
            LogChannel::CACHE->value => 'cache',
            LogChannel::DATABASE->value => 'database',
            LogChannel::FRAMEWORK->value => 'framework',
            LogChannel::ERROR->value => 'error',
            default => 'app'
        };

        return $this->logConfig->getLogPath($logPathKey);
    }

    /**
     * Formatiert die Log-Nachricht
     */
    private function formatMessage(LogRecord $record): string
    {
        $replacements = [
            '{timestamp}' => $record->getFormattedTimestamp('Y-m-d H:i:s'),
            '{level_name}' => $record->getLevel()->getName(),
            '{channel}' => $record->getChannel() ?? 'app',
            '{message}' => $record->getMessage(),
        ];

        $formatted = str_replace(array_keys($replacements), array_values($replacements), $this->outputFormat);

        // Context hinzufügen, falls vorhanden
        $context = $record->getContext();
        if (! empty($context)) {
            $formatted .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }

        return $formatted . PHP_EOL;
    }

    /**
     * Schreibt die formatierte Nachricht in eine Datei
     */
    private function writeToFile(string $logFile, string $message): void
    {
        // Verzeichnis erstellen, falls es nicht existiert
        $directory = dirname($logFile);
        if (! is_dir($directory)) {
            if (! mkdir($directory, 0755, true) && ! is_dir($directory)) {
                // Fehler beim Erstellen des Verzeichnisses - verwende Fallback
                error_log("Failed to create log directory: $directory");

                return;
            }
        }

        // Datei-Handle aus Cache holen oder erstellen
        $cacheKey = $logFile;
        if (! isset($this->fileHandles[$cacheKey])) {
            $handle = fopen($logFile, 'a');
            if ($handle === false) {
                error_log("Failed to open log file: $logFile");

                return;
            }

            // Datei-Berechtigungen setzen
            chmod($logFile, $this->fileMode);

            $this->fileHandles[$cacheKey] = $handle;
        }

        // In Datei schreiben
        fwrite($this->fileHandles[$cacheKey], $message);
        fflush($this->fileHandles[$cacheKey]);
    }

    /**
     * Schließt alle geöffneten Datei-Handles dieser Instanz
     */
    public function closeAllHandles(): void
    {
        foreach ($this->fileHandles as $handle) {
            if (is_resource($handle)) {
                fclose($handle);
            }
        }
        $this->fileHandles = [];
    }

    /**
     * Destruktor - schließt alle Datei-Handles
     */
    public function __destruct()
    {
        $this->closeAllHandles();
    }
}
