<?php

declare(strict_types=1);

namespace App\Framework\Logging;

use App\Framework\Attributes\Singleton;
use App\Framework\DateTime\Clock;
use App\Framework\Logging\ValueObjects\LogContext;
use DateMalformedStringException;

/**
 * Einfacher Logger für das Framework.
 */
#[Singleton]
final readonly class DefaultLogger implements Logger, SupportsChannels
{
    private ChannelLoggerRegistry $channelRegistry;

    /**
     * @param Clock $clock Clock für Timestamp-Generierung
     * @param LogLevel $minLevel Minimales Level, das geloggt werden soll
     * @param array<LogHandler> $handlers Array von Log-Handlern
     * @param ProcessorManager $processorManager Processor Manager für die Verarbeitung
     * @param LogContextManager|null $contextManager Optional: Context Manager für automatische Kontext-Anreicherung
     */
    public function __construct(
        private Clock              $clock,
        private LogLevel           $minLevel = LogLevel::DEBUG,
        /** @var LogHandler[] */
        private array              $handlers = [],
        private ProcessorManager   $processorManager = new ProcessorManager(),
        private ?LogContextManager $contextManager = null,
    ) {
        // Channel-Logger-Registry initialisieren
        $this->channelRegistry = new ChannelLoggerRegistry($this);
    }

    public function debug(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    public function info(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    public function notice(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    public function warning(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    public function error(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    public function critical(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    public function alert(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    public function emergency(string $message, ?LogContext $context = null): void
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Log-Nachricht mit beliebigem Level erstellen
     *
     * @param LogLevel $level Log-Level
     * @param string $message Log-Nachricht
     * @param LogContext|null $context Strukturierter LogContext
     * @throws DateMalformedStringException
     */
    public function log(LogLevel $level, string $message, ?LogContext $context = null): void
    {
        $this->createAndProcessRecord($level, $message, $context);
    }

    /**
     * Loggt in einen spezifischen Channel
     *
     * @internal Wird von ChannelLogger verwendet
     */
    public function logToChannel(LogChannel $channel, LogLevel $level, string $message, ?LogContext $context = null): void
    {
        $this->createAndProcessRecord($level, $message, $context, $channel->value);
    }

    /**
     * Erstellt und verarbeitet einen Log-Record
     *
     * @param LogLevel $level Log-Level
     * @param string $message Log-Nachricht
     * @param LogContext|null $context Strukturierter LogContext
     * @param string|null $channel Optional: Channel-Name
     * @throws DateMalformedStringException
     */
    private function createAndProcessRecord(LogLevel $level, string $message, ?LogContext $context = null, ?string $channel = null): void
    {
        // Wenn kein Context übergeben, leeren Context erstellen
        if ($context === null) {
            $context = LogContext::empty();
        }

        // Prüfen ob Level hoch genug ist
        if ($level->isLowerThan($this->minLevel)) {
            return;
        }

        // LogContext automatisch mit aktuellem Context anreichern
        $finalContext = $this->enrichWithCurrentContext($context);

        // Log-Record erstellen
        $record = new LogRecord(
            message: $message,
            context: $finalContext,
            level: $level,
            timestamp: $this->clock->now(),
            channel: $channel
        );

        // Record durch alle Processors verarbeiten
        $processedRecord = $this->processorManager->processRecord($record);

        // Alle Handler durchlaufen
        foreach ($this->handlers as $handler) {
            if ($handler->isHandling($processedRecord)) {

                $array = explode('\\',$handler::class);
                $handlerName = end($array);

                // Log-Record erstellen
                $record = new LogRecord(
                    message: $handlerName . ' --- ' . $message,
                    context: $finalContext,
                    level: $level,
                    timestamp: $this->clock->now(),
                    channel: $channel
                );
                $processedRecord = $this->processorManager->processRecord($record);

                $handler->handle($processedRecord);
            }
        }
    }

    /**
     * Reichert den übergebenen Context mit dem aktuellen Context vom LogContextManager an
     */
    private function enrichWithCurrentContext(LogContext $context): LogContext
    {
        // Wenn kein ContextManager verfügbar ist, Context unverändert zurückgeben
        if ($this->contextManager === null) {
            return $context;
        }

        $currentContext = $this->contextManager->getCurrentContext();

        // Wenn der übergebene Context ein LogContext ist, mit aktuellem Context mergen
        return $currentContext->merge($context);
    }


    /**
     * Holt einen ChannelLogger für einen spezifischen Channel
     */
    public function channel(LogChannel|string $channel): Logger&HasChannel
    {
        return $this->channelRegistry->get($channel);
    }

    /**
     * Gibt die aktuelle Konfiguration des Loggers zurück
     */
    public function getConfiguration(): array
    {
        return [
            'minLevel' => $this->minLevel->value,
            'handlers' => array_map(fn (LogHandler $h) => get_class($h), $this->handlers),
            'processors' => $this->processorManager->getProcessorList(),
            'registeredChannels' => $this->channelRegistry->getRegisteredChannels(),
        ];
    }
}
