<?php

declare(strict_types=1);

namespace App\Framework\Tracing\Exporters;

use App\Framework\Tracing\TraceExporter;

/**
 * Console Trace Exporter
 *
 * Exports traces to console output for development and debugging.
 * Provides formatted, human-readable trace output.
 */
final readonly class ConsoleTraceExporter implements TraceExporter
{
    public function __construct(
        private bool $colored = true,
        private bool $verbose = false,
        private int $maxDepth = 10
    ) {
    }

    public function export(array $traceData): void
    {
        $output = $this->formatTrace($traceData);
        echo $output . "\n";
    }

    private function formatTrace(array $traceData): string
    {
        $traceId = $traceData['traceId'] ?? 'unknown';
        $duration = $traceData['duration'] ?? 0;
        $spans = $traceData['spans'] ?? [];

        $header = $this->colorize("🔍 TRACE: {$traceId}", 'cyan', true);
        $summary = $this->colorize("Duration: {$this->formatDuration($duration)} | Spans: " . count($spans), 'gray');

        $output = [$header, $summary, ''];

        // Build span hierarchy
        $spanTree = $this->buildSpanTree($spans);
        $output[] = $this->renderSpanTree($spanTree);

        if ($this->verbose && ! empty($traceData['tags'] ?? [])) {
            $output[] = "\n" . $this->colorize("📋 Trace Tags:", 'yellow', true);
            foreach ($traceData['tags'] as $key => $value) {
                $output[] = "  {$key}: " . $this->formatValue($value);
            }
        }

        return implode("\n", $output);
    }

    private function buildSpanTree(array $spans): array
    {
        $spanMap = [];
        $rootSpans = [];

        $generator = new \App\Framework\Ulid\UlidGenerator();
        // First, create a map of all spans
        foreach ($spans as $span) {
            $spanId = $span['spanId'] ?? $generator->generate();
            $spanMap[$spanId] = array_merge($span, ['children' => []]);
        }

        // Then, build the tree structure
        foreach ($spanMap as $spanId => $span) {
            $parentId = $span['parentSpanId'] ?? null;

            if ($parentId && isset($spanMap[$parentId])) {
                $spanMap[$parentId]['children'][] = &$spanMap[$spanId];
            } else {
                $rootSpans[] = &$spanMap[$spanId];
            }
        }

        return $rootSpans;
    }

    private function renderSpanTree(array $spans, int $depth = 0): string
    {
        if ($depth > $this->maxDepth) {
            return $this->colorize("... (max depth exceeded)", 'gray');
        }

        $output = [];

        foreach ($spans as $span) {
            $indent = str_repeat('  ', $depth);
            $prefix = $depth > 0 ? '└─ ' : '';

            $name = $span['name'] ?? 'unnamed';
            $operation = $span['operation'] ?? 'unknown';
            $duration = $span['duration'] ?? 0;
            $hasError = $span['hasError'] ?? false;

            $statusIcon = $hasError ? $this->colorize('❌', 'red') : $this->colorize('✅', 'green');
            $nameText = $this->colorize($name, $hasError ? 'red' : 'white', true);
            $operationText = $this->colorize("({$operation})", 'gray');
            $durationText = $this->colorize($this->formatDuration($duration), 'blue');

            $line = "{$indent}{$prefix}{$statusIcon} {$nameText} {$operationText} {$durationText}";

            if ($hasError && ! empty($span['errorMessage'])) {
                $line .= "\n{$indent}   " . $this->colorize("Error: " . $span['errorMessage'], 'red');
            }

            if ($this->verbose && ! empty($span['tags'] ?? [])) {
                foreach ($span['tags'] as $key => $value) {
                    $line .= "\n{$indent}   {$key}: " . $this->formatValue($value);
                }
            }

            if ($this->verbose && ! empty($span['events'] ?? [])) {
                foreach ($span['events'] as $event) {
                    $eventName = $event['name'] ?? 'event';
                    $eventTime = $this->formatTimestamp($event['timestamp'] ?? 0);
                    $line .= "\n{$indent}   📝 {$eventName} @ {$eventTime}";
                }
            }

            $output[] = $line;

            // Render children
            if (! empty($span['children'])) {
                $output[] = $this->renderSpanTree($span['children'], $depth + 1);
            }
        }

        return implode("\n", $output);
    }

    private function formatDuration(float $duration): string
    {
        if ($duration < 0.001) {
            return number_format($duration * 1000000, 0) . 'μs';
        } elseif ($duration < 1) {
            return number_format($duration * 1000, 2) . 'ms';
        } else {
            return number_format($duration, 3) . 's';
        }
    }

    private function formatTimestamp(float $timestamp): string
    {
        return date('H:i:s.v', (int)$timestamp);
    }

    private function formatValue(mixed $value): string
    {
        if (is_string($value)) {
            return $this->colorize("\"$value\"", 'green');
        } elseif (is_numeric($value)) {
            return $this->colorize((string)$value, 'cyan');
        } elseif (is_bool($value)) {
            return $this->colorize($value ? 'true' : 'false', 'yellow');
        } elseif (is_array($value)) {
            return $this->colorize(json_encode($value, JSON_UNESCAPED_SLASHES), 'gray');
        } else {
            return $this->colorize(json_encode($value), 'gray');
        }
    }

    private function colorize(string $text, string $color, bool $bold = false): string
    {
        if (! $this->colored) {
            return $text;
        }

        $colors = [
            'red' => '31',
            'green' => '32',
            'yellow' => '33',
            'blue' => '34',
            'cyan' => '36',
            'white' => '37',
            'gray' => '90',
        ];

        $colorCode = $colors[$color] ?? '37';
        $boldCode = $bold ? '1;' : '';

        return "\033[{$boldCode}{$colorCode}m{$text}\033[0m";
    }
}
