<?php

// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
declare(strict_types=1);

namespace App\Services\Logging;

use DateTimeImmutable;

/**
 * Einfacher File-Logger für die AdminTool-Anwendung.
 *
 * Ziele:
 * - Technische Details werden in eine Log-Datei unter public/logs/ geschrieben.
 * - In der Weboberfläche erscheinen nur verständliche, fachliche Fehlermeldungen.
 */
class LoggingService
{
    /** @var string Vollständiger Pfad zum Log-Verzeichnis */
    private string $logDir;

    /** @var string Dateiname der Log-Datei */
    private string $logFile;

    /** @var int Minimale Log-Stufe, ab der geschrieben wird. */
    private int $minLevel;

    /**
     * Zuordnung der Log-Level zu numerischen Werten zur Filterung.
     *
     * @var array<string, int>
     */
    private const LEVEL_MAP = [
        'debug'   => 100,
        'info'    => 200,
        'warning' => 300,
        'error'   => 400,
    ];

    /**
     * @param array<string, mixed> $config Teilkonfiguration "logging" aus config.php
     */
    public function __construct(array $config)
    {
        // Standard: public/logs relativ zum Projektroot
        $baseDir = $config['log_dir'] ?? (__DIR__ . '/../../../public/logs');
        $fileName = $config['log_file'] ?? 'app.log';
        $level = strtolower((string)($config['min_level'] ?? 'info'));

        $this->logDir = rtrim($baseDir, DIRECTORY_SEPARATOR);
        $this->logFile = $fileName;
        $this->minLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['info'];

        $this->ensureLogDirectoryExists();
    }

    /**
     * Stellt sicher, dass das Log-Verzeichnis existiert.
     */
    private function ensureLogDirectoryExists(): void
    {
        if (is_dir($this->logDir) === true) {
            return;
        }

        if (@mkdir($this->logDir, 0775, true) === false && is_dir($this->logDir) === false) {
            // Wenn das Anlegen fehlschlägt, wenigstens einen Eintrag im PHP-Error-Log hinterlassen.
            error_log(sprintf('LoggingService: Konnte Log-Verzeichnis "%s" nicht anlegen.', $this->logDir));
        }
    }

    /**
     * Allgemeiner Log-Eintrag.
     *
     * @param string $level   Log-Level (debug|info|warning|error)
     * @param string $message Nachrichtentext
     * @param array<string, mixed> $context Zusätzliche Kontextinformationen
     */
    public function log(string $level, string $message, array $context = []): void
    {
        $level = strtolower($level);
        $numericLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['error'];

        // Alles unterhalb der minimalen Stufe ignorieren.
        if ($numericLevel < $this->minLevel) {
            return;
        }

        $timestamp = (new DateTimeImmutable())->format('Y-m-d H:i:s');

        $contextJson = $context === []
            ? '{}'
            : (string)json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $line = sprintf(
            "[%s] %-7s %s %s%s",
            $timestamp,
            strtoupper($level),
            $message,
            $contextJson,
            PHP_EOL
        );

        $filePath = $this->logDir . DIRECTORY_SEPARATOR . $this->logFile;

        if (@file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX) === false) {
            // Fallback, damit Fehler beim Logging selbst nicht die App zerschießen.
            error_log(sprintf('LoggingService: Konnte in Log-Datei "%s" nicht schreiben.', $filePath));
        }
    }

    /**
     * Komfortmethode, um Exceptions strukturiert zu loggen.
     *
     * @param string $message   Kurzer Kontexttext zur Exception
     * @param \Throwable $exception Die geworfene Exception
     * @param array<string, mixed> $context Zusätzlicher Kontext (Route, Benutzername, Remote-IP, ...)
     */
    public function logException(string $message, \Throwable $exception, array $context = []): void
    {
        $exceptionContext = [
            'exception_class'   => get_class($exception),
            'exception_message' => $exception->getMessage(),
            'file'              => $exception->getFile(),
            'line'              => $exception->getLine(),
            'trace'             => $exception->getTraceAsString(),
        ];

        $mergedContext = array_merge($context, $exceptionContext);

        $this->log('error', $message, $mergedContext);
    }
}
