<?php

declare(strict_types=1);

namespace App\Framework\Performance;

class PerformanceMeter
{
    /** @var array<string, PerformanceMarker> */
    private array $markers = [];

    /** @var array<string, PerformanceMeasurement> */
    private array $activeMeasurements = [];

    /** @var array<string, array<int, PerformanceMeasurement>> */
    private array $completedMeasurements = [];

    private float $initTime;
    private int $initMemory;
    private bool $enabled;

    public function __construct(bool $enabled = true)
    {
        $this->initTime = microtime(true);
        $this->initMemory = memory_get_usage();
        $this->enabled = $enabled;

        // Automatisch den ersten Marker setzen
        $this->mark('init', PerformanceCategory::SYSTEM);
    }

    /**
     * Setzt einen Marker mit einem Label und einer Kategorie
     */
    public function mark(string $label, PerformanceCategory $category = PerformanceCategory::CUSTOM): void
    {
        if (!$this->enabled) {
            return;
        }

        $this->markers[$label] = new PerformanceMarker($label, $category);
    }

    /**
     * Startet eine Zeitmessung für einen bestimmten Prozess
     */
    public function startMeasure(string $label, PerformanceCategory $category = PerformanceCategory::CUSTOM): void
    {
        if (!$this->enabled) {
            return;
        }

        $this->activeMeasurements[$label] = new PerformanceMeasurement($label, $category);
    }

    /**
     * Beendet eine Zeitmessung und speichert das Ergebnis
     */
    public function endMeasure(string $label): void
    {
        if (!$this->enabled || !isset($this->activeMeasurements[$label])) {
            return;
        }

        $measurement = $this->activeMeasurements[$label];
        $measurement->end();

        $category = $measurement->getCategory()->value;

        if (!isset($this->completedMeasurements[$category])) {
            $this->completedMeasurements[$category] = [];
        }

        $this->completedMeasurements[$category][] = $measurement;

        // Aktive Messung entfernen
        unset($this->activeMeasurements[$label]);

        // Marker zur einfachen zeitlichen Einordnung setzen
        $this->mark("{$label}_end", $measurement->getCategory());
    }

    /**
     * Führt eine Funktion aus und misst deren Performance
     */
    public function measure(
        string $label,
        callable $callback,
        PerformanceCategory $category = PerformanceCategory::CUSTOM
    ): mixed {
        if (!$this->enabled) {
            return $callback();
        }

        $this->startMeasure($label, $category);
        $result = $callback();
        $this->endMeasure($label);

        return $result;
    }

    /**
     * Gibt die Zeit zwischen zwei Markern zurück
     */
    public function getTimeBetween(string $startLabel, string $endLabel): float
    {
        if (!isset($this->markers[$startLabel]) || !isset($this->markers[$endLabel])) {
            return 0.0;
        }

        return ($this->markers[$endLabel]->getTime() - $this->markers[$startLabel]->getTime()) * 1000;
    }

    /**
     * Gibt den Speicherverbrauch zwischen zwei Markern zurück
     */
    public function getMemoryBetween(string $startLabel, string $endLabel): float
    {
        if (!isset($this->markers[$startLabel]) || !isset($this->markers[$endLabel])) {
            return 0.0;
        }

        return ($this->markers[$endLabel]->getMemory() - $this->markers[$startLabel]->getMemory()) / 1024 / 1024;
    }

    /**
     * Gibt alle Marker zurück, gefiltert nach Kategorie
     */
    public function getMarkers(?PerformanceCategory $category = null): array
    {
        if ($category === null) {
            return $this->markers;
        }

        return array_filter(
            $this->markers,
            fn (PerformanceMarker $marker): bool => $marker->getCategory() === $category
        );
    }

    /**
     * Gibt einen Bericht über alle Marker aus
     */
    public function generateReport(): array
    {
        $report = [
            'summary' => [
                'total_time_ms' => $this->getTotalTime(),
                'total_memory_mb' => $this->getTotalMemory(),
                'marker_count' => count($this->markers),
            ],
            'markers' => [],
            'measurements' => [],
            'categories' => [],
        ];

        // Marker chronologisch sortieren
        $sortedMarkers = $this->markers;
        uasort($sortedMarkers, fn($a, $b) => $a->getTime() <=> $b->getTime());

        // Marker-Bericht erstellen
        $previousTime = $this->initTime;
        foreach ($sortedMarkers as $label => $marker) {
            $timeSinceStart = ($marker->getTime() - $this->initTime) * 1000;
            $timeSincePrevious = ($marker->getTime() - $previousTime) * 1000;
            $memorySinceStart = ($marker->getMemory() - $this->initMemory) / 1024 / 1024;

            $report['markers'][$label] = [
                'category' => $marker->getCategory()->value,
                'time_since_start_ms' => round($timeSinceStart, 2),
                'time_since_previous_ms' => round($timeSincePrevious, 2),
                'memory_mb' => round($memorySinceStart, 2),
            ];

            $previousTime = $marker->getTime();
        }

        // Messungen nach Kategorien zusammenfassen
        foreach ($this->completedMeasurements as $categoryKey => $measurements) {
            $report['categories'][$categoryKey] = [
                'count' => count($measurements),
                'measurements' => [],
            ];

            foreach ($measurements as $measurement) {
                $label = $measurement->getLabel();

                if (!isset($report['measurements'][$label])) {
                    $report['measurements'][$label] = [
                        'category' => $measurement->getCategory()->value,
                        'count' => 0,
                        'total_time_ms' => 0,
                        'total_memory_mb' => 0,
                        'avg_time_ms' => 0,
                        'avg_memory_mb' => 0,
                        'min_time_ms' => PHP_FLOAT_MAX,
                        'max_time_ms' => 0,
                    ];
                }

                $duration = $measurement->getDurationMs();
                $memory = $measurement->getMemoryUsageMb();

                $data = &$report['measurements'][$label];
                $data['count']++;
                $data['total_time_ms'] += $duration;
                $data['total_memory_mb'] += $memory;
                $data['min_time_ms'] = min($data['min_time_ms'], $duration);
                $data['max_time_ms'] = max($data['max_time_ms'], $duration);
                $data['avg_time_ms'] = $data['total_time_ms'] / $data['count'];
                $data['avg_memory_mb'] = $data['total_memory_mb'] / $data['count'];

                // Einzelmessungen pro Kategorie
                $report['categories'][$categoryKey]['measurements'][] = [
                    'label' => $label,
                    'time_ms' => round($duration, 2),
                    'memory_mb' => round($memory, 2),
                    'start_time' => ($measurement->getStartTime() - $this->initTime) * 1000,
                    'end_time' => ($measurement->getEndTime() - $this->initTime) * 1000,
                ];
            }

            // Statistiken für die Kategorie berechnen
            $categoryMeasurements = $report['categories'][$categoryKey]['measurements'];
            if (!empty($categoryMeasurements)) {
                $totalTime = array_sum(array_column($categoryMeasurements, 'time_ms'));
                $totalMemory = array_sum(array_column($categoryMeasurements, 'memory_mb'));
                $count = count($categoryMeasurements);

                $report['categories'][$categoryKey]['total_time_ms'] = $totalTime;
                $report['categories'][$categoryKey]['avg_time_ms'] = $totalTime / $count;
                $report['categories'][$categoryKey]['total_memory_mb'] = $totalMemory;
                $report['categories'][$categoryKey]['avg_memory_mb'] = $totalMemory / $count;
            }
        }

        // Rundungen für alle numerischen Werte
        foreach ($report['measurements'] as &$measurement) {
            $measurement['avg_time_ms'] = round($measurement['avg_time_ms'], 2);
            $measurement['avg_memory_mb'] = round($measurement['avg_memory_mb'], 2);
            $measurement['total_time_ms'] = round($measurement['total_time_ms'], 2);
            $measurement['total_memory_mb'] = round($measurement['total_memory_mb'], 2);
            $measurement['min_time_ms'] = round($measurement['min_time_ms'], 2);
            $measurement['max_time_ms'] = round($measurement['max_time_ms'], 2);
        }

        return $report;
    }

    /**
     * Formatiert einen Bericht als HTML
     */
    public function generateHtmlReport(): string
    {
        $report = $this->generateReport();

        $html = '<div style="font-family: monospace; font-size: 13px; background: #f5f5f5; padding: 10px; border: 1px solid #ddd;">';
        $html .= '<h3>Performance-Bericht</h3>';
        $html .= '<p>';
        $html .= sprintf('Gesamtzeit: <strong>%.2f ms</strong><br>', $report['summary']['total_time_ms']);
        $html .= sprintf('Speicherverbrauch: <strong>%.2f MB</strong><br>', $report['summary']['total_memory_mb']);
        $html .= sprintf('Anzahl Marker: <strong>%d</strong>', $report['summary']['marker_count']);
        $html .= '</p>';

        if (!empty($report['measurements'])) {
            $html .= '<h4>Messungen</h4>';
            $html .= '<table style="border-collapse: collapse; width: 100%;">';
            $html .= '<tr style="background: #eee;"><th style="text-align: left; padding: 5px;">Label</th><th style="text-align: left; padding: 5px;">Kategorie</th><th style="text-align: right; padding: 5px;">Anzahl</th><th style="text-align: right; padding: 5px;">Avg. Zeit (ms)</th><th style="text-align: right; padding: 5px;">Min/Max (ms)</th><th style="text-align: right; padding: 5px;">Avg. Speicher (MB)</th></tr>';

            foreach ($report['measurements'] as $label => $data) {
                $html .= sprintf(
                    '<tr style="border-bottom: 1px solid #ddd;"><td style="padding: 5px;">%s</td><td style="padding: 5px;">%s</td><td style="text-align: right; padding: 5px;">%d</td><td style="text-align: right; padding: 5px;">%.2f</td><td style="text-align: right; padding: 5px;">%.2f / %.2f</td><td style="text-align: right; padding: 5px;">%.2f</td></tr>',
                    htmlspecialchars($label),
                    htmlspecialchars($data['category']),
                    $data['count'],
                    $data['avg_time_ms'],
                    $data['min_time_ms'],
                    $data['max_time_ms'],
                    $data['avg_memory_mb']
                );
            }

            $html .= '</table>';
        }

        $html .= '<h4>Marker-Zeitleiste</h4>';
        $html .= '<table style="border-collapse: collapse; width: 100%;">';
        $html .= '<tr style="background: #eee;"><th style="text-align: left; padding: 5px;">Zeit (ms)</th><th style="text-align: left; padding: 5px;">Label</th><th style="text-align: left; padding: 5px;">Kategorie</th><th style="text-align: right; padding: 5px;">+Zeit (ms)</th><th style="text-align: right; padding: 5px;">Speicher (MB)</th></tr>';

        foreach ($report['markers'] as $label => $data) {
            $html .= sprintf(
                '<tr style="border-bottom: 1px solid #ddd;"><td style="padding: 5px;">%.2f</td><td style="padding: 5px;">%s</td><td style="padding: 5px;">%s</td><td style="text-align: right; padding: 5px;">%.2f</td><td style="text-align: right; padding: 5px;">%.2f</td></tr>',
                $data['time_since_start_ms'],
                htmlspecialchars($label),
                htmlspecialchars($data['category']),
                $data['time_since_previous_ms'],
                $data['memory_mb']
            );
        }

        $html .= '</table>';

        // Kategorieübersicht
        if (!empty($report['categories'])) {
            $html .= '<h4>Kategorien</h4>';
            $html .= '<table style="border-collapse: collapse; width: 100%;">';
            $html .= '<tr style="background: #eee;"><th style="text-align: left; padding: 5px;">Kategorie</th><th style="text-align: right; padding: 5px;">Anzahl</th><th style="text-align: right; padding: 5px;">Gesamtzeit (ms)</th><th style="text-align: right; padding: 5px;">Durchschnitt (ms)</th></tr>';

            foreach ($report['categories'] as $category => $data) {
                if (isset($data['total_time_ms'])) {
                    $html .= sprintf(
                        '<tr style="border-bottom: 1px solid #ddd;"><td style="padding: 5px;">%s</td><td style="text-align: right; padding: 5px;">%d</td><td style="text-align: right; padding: 5px;">%.2f</td><td style="text-align: right; padding: 5px;">%.2f</td></tr>',
                        htmlspecialchars($category),
                        $data['count'],
                        $data['total_time_ms'],
                        $data['avg_time_ms']
                    );
                }
            }

            $html .= '</table>';
        }

        $html .= '</div>';

        return $html;
    }

    /**
     * Gibt einen Bericht als Text aus
     */
    public function generateTextReport(): string
    {
        $report = $this->generateReport();

        $text = "== Performance-Bericht ==\n";
        $text .= sprintf("Gesamtzeit: %.2f ms\n", $report['summary']['total_time_ms']);
        $text .= sprintf("Speicherverbrauch: %.2f MB\n", $report['summary']['total_memory_mb']);
        $text .= sprintf("Anzahl Marker: %d\n\n", $report['summary']['marker_count']);

        if (!empty($report['measurements'])) {
            $text .= "== Messungen ==\n";
            $text .= sprintf("%-30s %-15s %10s %15s %15s %15s\n",
                "Label", "Kategorie", "Anzahl", "Avg. Zeit (ms)", "Min/Max (ms)", "Avg. Speicher (MB)");
            $text .= str_repeat("-", 100) . "\n";

            foreach ($report['measurements'] as $label => $data) {
                $text .= sprintf(
                    "%-30s %-15s %10d %15.2f %15s %15.2f\n",
                    $label,
                    $data['category'],
                    $data['count'],
                    $data['avg_time_ms'],
                    sprintf("%.2f/%.2f", $data['min_time_ms'], $data['max_time_ms']),
                    $data['avg_memory_mb']
                );
            }

            $text .= "\n";
        }

        $text .= "== Marker-Zeitleiste ==\n";
        $text .= sprintf("%-10s %-30s %-15s %15s %15s\n",
            "Zeit (ms)", "Label", "Kategorie", "+Zeit (ms)", "Speicher (MB)");
        $text .= str_repeat("-", 90) . "\n";

        foreach ($report['markers'] as $label => $data) {
            $text .= sprintf(
                "%-10.2f %-30s %-15s %15.2f %15.2f\n",
                $data['time_since_start_ms'],
                $label,
                $data['category'],
                $data['time_since_previous_ms'],
                $data['memory_mb']
            );
        }

        if (!empty($report['categories'])) {
            $text .= "\n== Kategorien ==\n";
            $text .= sprintf("%-15s %10s %20s %20s\n",
                "Kategorie", "Anzahl", "Gesamtzeit (ms)", "Durchschnitt (ms)");
            $text .= str_repeat("-", 70) . "\n";

            foreach ($report['categories'] as $category => $data) {
                if (isset($data['total_time_ms'])) {
                    $text .= sprintf(
                        "%-15s %10d %20.2f %20.2f\n",
                        $category,
                        $data['count'],
                        $data['total_time_ms'],
                        $data['avg_time_ms']
                    );
                }
            }
        }

        return $text;
    }

    /**
     * Gibt die Gesamtausführungszeit zurück
     */
    public function getTotalTime(): float
    {
        return (microtime(true) - $this->initTime) * 1000; // in ms
    }

    /**
     * Gibt den Gesamtspeicherverbrauch zurück
     */
    public function getTotalMemory(): float
    {
        return (memory_get_usage() - $this->initMemory) / 1024 / 1024; // in MB
    }

    /**
     * Aktiviert oder deaktiviert den PerformanceMeter
     */
    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    /**
     * Gibt zurück, ob der PerformanceMeter aktiviert ist
     */
    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    /**
     * Setzt alle Messungen zurück
     */
    public function reset(): void
    {
        $this->markers = [];
        $this->activeMeasurements = [];
        $this->completedMeasurements = [];
        $this->initTime = microtime(true);
        $this->initMemory = memory_get_usage();

        // Automatisch den ersten Marker setzen
        $this->mark('init', PerformanceCategory::SYSTEM);
    }
}
