<?php

declare(strict_types=1);

namespace App\Framework\Logging\Handlers;

use App\Framework\Core\PathProvider;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\LogRotator;

/**
 * Handler für die Ausgabe von Log-Einträgen in Dateien.
 */
class FileHandler implements LogHandler
{
    /**
     * @var LogLevel Minimales Level, ab dem dieser Handler aktiv wird
     */
    private LogLevel $minLevel;

    /**
     * @var string Pfad zur Log-Datei
     */
    private string $logFile;

    /**
     * @var string Format für die Ausgabe
     */
    private string $outputFormat;

    /**
     * @var int Datei-Modi für die Log-Datei
     */
    private int $fileMode;

    /**
     * @var LogRotator|null Log-Rotator für automatische Rotation
     */
    private ?LogRotator $rotator = null;

    /**
     * @var PathProvider|null PathProvider für die Auflösung von Pfaden
     */
    private ?PathProvider $pathProvider = null;

    /**
     * Erstellt einen neuen FileHandler
     *
     * @param string $logFile Pfad zur Log-Datei
     * @param LogLevel|int $minLevel Minimales Level, ab dem dieser Handler aktiv wird
     * @param string $outputFormat Format für die Ausgabe
     * @param int $fileMode Datei-Modi für die Log-Datei
     * @param LogRotator|null $rotator Optional: Log-Rotator für automatische Rotation
     * @param PathProvider|null $pathProvider Optional: PathProvider für die Auflösung von Pfaden
     */
    public function __construct(
        string $logFile,
        LogLevel|int $minLevel = LogLevel::DEBUG,
        string $outputFormat = '[{timestamp}] [{level_name}] {request_id}{channel}{message}',
        int $fileMode = 0644,
        ?LogRotator $rotator = null,
        ?PathProvider $pathProvider = null
    ) {
        $this->pathProvider = $pathProvider;

        // Pfad auflösen, falls PathProvider vorhanden
        if ($this->pathProvider !== null && ! str_starts_with($logFile, '/')) {
            $logFile = $this->pathProvider->resolvePath($logFile);
        }

        $this->logFile = $logFile;
        $this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
        $this->outputFormat = $outputFormat;
        $this->fileMode = $fileMode;
        $this->rotator = $rotator;

        // Stelle sicher, dass das Verzeichnis existiert
        $this->ensureDirectoryExists(dirname($logFile));
    }

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

    /**
     * Verarbeitet einen Log-Eintrag
     */
    public function handle(LogRecord $record): void
    {
        // Request-ID-Teil erstellen, falls vorhanden
        $requestId = $record->hasExtra('request_id')
            ? "[{$record->getExtra('request_id')}] "
            : '';

        // Channel-Teil erstellen, falls vorhanden
        $channel = $record->getChannel()
            ? "[{$record->getChannel()}] "
            : '';

        // Werte für Platzhalter im Format
        $values = [
            '{level_name}' => $record->getLevel()->getName(),
            '{timestamp}' => $record->getFormattedTimestamp(),
            '{request_id}' => $requestId,
            '{channel}' => $channel,
            '{message}' => $record->getMessage(),
        ];

        // Formatierte Ausgabe erstellen
        $output = strtr($this->outputFormat, $values) . PHP_EOL;

        // Prüfe Rotation vor dem Schreiben
        if ($this->rotator !== null) {
            $this->rotator->rotateIfNeeded($this->logFile);
        }

        // In die Datei schreiben
        $this->write($output);

        // Bei kritischen Fehlern Cache leeren, um sicherzustellen, dass der Eintrag geschrieben wird
        if ($record->getLevel()->value >= LogLevel::CRITICAL->value) {
            $this->flush();
        }
    }

    /**
     * Schreibt einen String in die Log-Datei
     */
    protected function write(string $output): void
    {
        file_put_contents($this->logFile, $output, FILE_APPEND);
    }

    /**
     * Leert Cache und stellt sicher, dass alles geschrieben wurde
     */
    protected function flush(): void
    {
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }
    }

    /**
     * Stellt sicher, dass ein Verzeichnis existiert
     */
    private function ensureDirectoryExists(string $dir): void
    {
        if (! file_exists($dir)) {
            mkdir($dir, 0777, true);
        }
    }

    /**
     * Minimales Log-Level setzen
     */
    public function setMinLevel(LogLevel|int $level): self
    {
        $this->minLevel = $level instanceof LogLevel ? $level : LogLevel::fromValue($level);

        return $this;
    }

    /**
     * Ausgabeformat setzen
     */
    public function setOutputFormat(string $format): self
    {
        $this->outputFormat = $format;

        return $this;
    }

    /**
     * Log-Datei setzen
     */
    public function setLogFile(string $logFile): self
    {
        // Pfad auflösen, falls PathProvider vorhanden
        if ($this->pathProvider !== null && ! str_starts_with($logFile, '/')) {
            $logFile = $this->pathProvider->resolvePath($logFile);
        }

        $this->logFile = $logFile;
        $this->ensureDirectoryExists(dirname($logFile));

        return $this;
    }
}
