<?php

declare(strict_types=1);

namespace App\Framework\Logging\Formatter;

use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;

/**
 * Development Formatter für human-readable Debug Output
 */
final readonly class DevelopmentFormatter implements LogFormatter
{
    public function __construct(
        private bool $includeStackTrace = true,
        private bool $colorOutput = true
    ) {
    }

    public function __invoke(LogRecord $record): string
    {
        $level = $record->getLevel();
        $timestamp = $record->getFormattedTimestamp('H:i:s.u');
        $channel = $record->getChannel() ?? 'app';
        $message = $record->getMessage();

        // Color coding for levels
        $levelString = $this->colorOutput ? $this->colorizeLevel($level) : $level->getName();

        $output = sprintf(
            "%s [%s] %s.%s: %s\n",
            $timestamp,
            $levelString,
            $channel,
            $level->getName(),
            $message
        );

        // Add context if present
        $context = $record->getContext();
        if (! empty($context)) {
            $output .= $this->formatContext($context);
        }

        // Add extras (like exception details)
        $extras = $record->getExtras();
        if (! empty($extras)) {
            $output .= $this->formatExtras($extras);
        }

        return $output;
    }

    private function colorizeLevel(LogLevel $level): string
    {
        if (! $this->colorOutput) {
            return $level->getName();
        }

        return match($level) {
            LogLevel::DEBUG => "\033[36m" . $level->getName() . "\033[0m",     // Cyan
            LogLevel::INFO => "\033[32m" . $level->getName() . "\033[0m",      // Green
            LogLevel::NOTICE => "\033[34m" . $level->getName() . "\033[0m",    // Blue
            LogLevel::WARNING => "\033[33m" . $level->getName() . "\033[0m",   // Yellow
            LogLevel::ERROR => "\033[31m" . $level->getName() . "\033[0m",     // Red
            LogLevel::CRITICAL => "\033[35m" . $level->getName() . "\033[0m",  // Magenta
            LogLevel::ALERT => "\033[41m" . $level->getName() . "\033[0m",     // Red background
            LogLevel::EMERGENCY => "\033[41;37m" . $level->getName() . "\033[0m", // Red bg, white text
        };
    }

    private function formatContext(array $context): string
    {
        $output = "  Context:\n";

        foreach ($context as $key => $value) {
            if (str_starts_with($key, '_')) {
                continue; // Skip internal context keys for cleaner output
            }

            $formattedValue = $this->formatValue($value);
            $output .= "    {$key}: {$formattedValue}\n";
        }

        return $output;
    }

    private function formatExtras(array $extras): string
    {
        $output = "";

        // Special handling for exception details
        if (isset($extras['exception_class'])) {
            $output .= $this->formatException($extras);
        }

        // Other extras
        $otherExtras = array_diff_key($extras, [
            'exception_class' => true,
            'exception_message' => true,
            'exception_file' => true,
            'exception_line' => true,
            'stack_trace' => true,
            'stack_trace_short' => true,
            'previous_exceptions' => true,
            'exception_hash' => true,
            'exception_severity' => true,
        ]);

        if (! empty($otherExtras)) {
            $output .= "  Extra:\n";
            foreach ($otherExtras as $key => $value) {
                $formattedValue = $this->formatValue($value);
                $output .= "    {$key}: {$formattedValue}\n";
            }
        }

        return $output;
    }

    private function formatException(array $extras): string
    {
        $output = "  Exception:\n";
        $output .= "    Class: {$extras['exception_class']}\n";
        $output .= "    File: {$extras['exception_file']}:{$extras['exception_line']}\n";

        if (isset($extras['exception_hash'])) {
            $output .= "    Hash: {$extras['exception_hash']} (severity: {$extras['exception_severity']})\n";
        }

        if ($this->includeStackTrace && isset($extras['stack_trace_short'])) {
            $output .= "    Trace: {$extras['stack_trace_short']}\n";
        }

        if (isset($extras['previous_exceptions']) && ! empty($extras['previous_exceptions'])) {
            $output .= "    Previous: " . count($extras['previous_exceptions']) . " exception(s)\n";
        }

        return $output;
    }

    private function formatValue(mixed $value): string
    {
        return match(true) {
            is_string($value) => '"' . $value . '"',
            is_array($value) => 'Array[' . count($value) . ']',
            is_object($value) => get_class($value) . '{}',
            is_bool($value) => $value ? 'true' : 'false',
            is_null($value) => 'null',
            default => (string) $value
        };
    }
}
