<?php

declare(strict_types=1);

namespace App\Framework\Performance;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\HighResolutionClock;
use App\Framework\Logging\Logger;
use App\Framework\Performance\ValueObjects\NestedMeasurement;

/**
 * Verwaltet verschachtelte Performance-Messungen mit Hierarchie-Support
 */
final class NestedPerformanceTracker
{
    /** @var NestedMeasurement[] Call stack der aktuell laufenden Operationen */
    private array $callStack = [];

    /** @var NestedMeasurement[] Abgeschlossene Top-Level Operationen */
    private array $completedOperations = [];

    /** @var array<string, NestedMeasurement> Lookup für alle aktiven Operationen */
    private array $activeOperations = [];

    private int $operationCounter = 0;

    private bool $enabled = true;

    public function __construct(
        private readonly Clock $clock,
        private readonly HighResolutionClock $highResClock,
        private readonly MemoryMonitor $memoryMonitor,
        private readonly ?Logger $logger = null
    ) {
    }

    /**
     * Start eine neue verschachtelte Operation
     */
    public function startOperation(
        string $name,
        PerformanceCategory $category,
        array $context = []
    ): string {
        if (! $this->enabled) {
            return '';
        }

        $operationId = $this->generateOperationId($name);
        $currentParent = $this->getCurrentOperation();
        $depth = count($this->callStack);

        $measurement = NestedMeasurement::start(
            operationId: $operationId,
            name: $name,
            category: $category,
            startTime: $this->clock->time(),
            startMemory: $this->memoryMonitor->getCurrentMemory(),
            context: $context,
            parentId: $currentParent?->operationId,
            depth: $depth
        );

        // Zum Call Stack hinzufügen
        $this->callStack[] = $measurement;
        $this->activeOperations[$operationId] = $measurement;

        $this->logger?->debug('Nested operation started', [
            'operation_id' => $operationId,
            'name' => $name,
            'category' => $category->value,
            'depth' => $depth,
            'parent_id' => $currentParent?->operationId,
        ]);

        return $operationId;
    }

    /**
     * Beende eine Operation
     */
    public function endOperation(string $operationId): ?NestedMeasurement
    {
        if (! $this->enabled || ! isset($this->activeOperations[$operationId])) {
            return null;
        }

        $measurement = $this->activeOperations[$operationId];

        // Vervollständige die Messung
        $completedMeasurement = $measurement->complete(
            $this->clock->time(),
            $this->memoryMonitor->getCurrentMemory()
        );

        // Aus aktiven Operationen entfernen
        unset($this->activeOperations[$operationId]);

        // Vom Call Stack entfernen
        $this->removeFromCallStack($operationId);

        // Zu Parent hinzufügen oder als Top-Level speichern
        $parent = $this->findParentOperation($completedMeasurement->parentId);
        if ($parent) {
            // Als Child zum Parent hinzufügen
            $updatedParent = $parent->addChild($completedMeasurement);
            $this->updateActiveOperation($parent->operationId, $updatedParent);
        } else {
            // Als Top-Level Operation speichern
            $this->completedOperations[$operationId] = $completedMeasurement;
        }

        $this->logger?->info('Nested operation completed', [
            'operation_id' => $operationId,
            'name' => $completedMeasurement->name,
            'duration_ms' => $completedMeasurement->duration?->toMilliseconds(),
            'self_time_ms' => $completedMeasurement->getSelfTime()->toMilliseconds(),
            'depth' => $completedMeasurement->depth,
            'children_count' => count($completedMeasurement->children),
        ]);

        return $completedMeasurement;
    }

    /**
     * Messe eine Operation mit automatischem Start/End
     */
    public function measure(
        string $name,
        PerformanceCategory $category,
        callable $callback,
        array $context = []
    ): mixed {
        if (! $this->enabled) {
            return $callback();
        }

        $operationId = $this->startOperation($name, $category, $context);

        try {
            return $callback();
        } finally {
            $this->endOperation($operationId);
        }
    }

    /**
     * Get aktuell laufende Operation (Top of stack)
     */
    public function getCurrentOperation(): ?NestedMeasurement
    {
        return end($this->callStack) ?: null;
    }

    /**
     * Get Call Stack depth
     */
    public function getCallStackDepth(): int
    {
        return count($this->callStack);
    }

    /**
     * Get alle aktiven Operationen
     */
    public function getActiveOperations(): array
    {
        return $this->activeOperations;
    }

    /**
     * Get abgeschlossene Top-Level Operationen
     */
    public function getCompletedOperations(): array
    {
        return $this->completedOperations;
    }

    /**
     * Get Operation nach ID
     */
    public function getOperation(string $operationId): ?NestedMeasurement
    {
        // Zuerst in aktiven suchen
        if (isset($this->activeOperations[$operationId])) {
            return $this->activeOperations[$operationId];
        }

        // Dann in abgeschlossenen suchen
        if (isset($this->completedOperations[$operationId])) {
            return $this->completedOperations[$operationId];
        }

        // In Children von abgeschlossenen suchen
        foreach ($this->completedOperations as $completed) {
            $found = $completed->findChild($operationId);
            if ($found) {
                return $found;
            }
        }

        return null;
    }

    /**
     * Get Performance-Bericht als hierarchische Struktur
     */
    public function getHierarchicalReport(): array
    {
        $report = [
            'active_operations' => [],
            'completed_operations' => [],
            'call_stack_depth' => $this->getCallStackDepth(),
            'total_operations' => count($this->activeOperations) + count($this->completedOperations),
            'summary' => $this->getSummary(),
        ];

        // Aktive Operationen
        foreach ($this->activeOperations as $active) {
            $report['active_operations'][] = $active->toArray();
        }

        // Abgeschlossene Operationen (hierarchisch)
        foreach ($this->completedOperations as $completed) {
            $report['completed_operations'][] = $completed->toHierarchicalArray();
        }

        return $report;
    }

    /**
     * Get flachen Performance-Bericht (alle Operationen linear)
     */
    public function getFlatReport(): array
    {
        $allOperations = [];

        // Aktive Operationen
        foreach ($this->activeOperations as $active) {
            $allOperations[] = $active->toArray();
        }

        // Alle abgeschlossenen (inklusive Children)
        foreach ($this->completedOperations as $completed) {
            $allOperations[] = $completed->toArray();
            $descendants = $completed->getAllDescendants();
            foreach ($descendants as $descendant) {
                $allOperations[] = $descendant->toArray();
            }
        }

        return $allOperations;
    }

    /**
     * Get Execution Tree als String (für Debug)
     */
    public function getExecutionTree(): string
    {
        $tree = "=== Nested Performance Execution Tree ===\n";

        if (! empty($this->callStack)) {
            $tree .= "Active Call Stack:\n";
            foreach ($this->callStack as $i => $operation) {
                $tree .= sprintf(
                    "%d. %s%s [%s] (running)\n",
                    $i + 1,
                    str_repeat('  ', $operation->depth),
                    $operation->name,
                    $operation->category->value
                );
            }
            $tree .= "\n";
        }

        if (! empty($this->completedOperations)) {
            $tree .= "Completed Operations:\n";
            foreach ($this->completedOperations as $completed) {
                $tree .= $completed->getExecutionTree();
            }
        }

        return $tree;
    }

    /**
     * Get Performance-Zusammenfassung
     */
    public function getSummary(): array
    {
        $totalOperations = count($this->activeOperations) + count($this->completedOperations);
        $completedCount = count($this->completedOperations);

        if ($completedCount === 0) {
            return [
                'total_operations' => $totalOperations,
                'completed_operations' => 0,
                'active_operations' => count($this->activeOperations),
                'average_duration_ms' => 0,
                'total_duration_ms' => 0,
                'max_depth' => $this->getCallStackDepth(),
            ];
        }

        // Berechne Statistiken für abgeschlossene Operationen
        $totalDuration = Duration::zero();
        $maxDepth = $this->getCallStackDepth();

        foreach ($this->completedOperations as $completed) {
            if ($completed->duration) {
                $totalDuration = $totalDuration->add($completed->duration);
            }

            $descendants = $completed->getAllDescendants();
            foreach ($descendants as $descendant) {
                $maxDepth = max($maxDepth, $descendant->depth + 1);
            }
        }

        return [
            'total_operations' => $totalOperations,
            'completed_operations' => $completedCount,
            'active_operations' => count($this->activeOperations),
            'average_duration_ms' => $completedCount > 0 ?
                $totalDuration->toMilliseconds() / $completedCount : 0,
            'total_duration_ms' => $totalDuration->toMilliseconds(),
            'max_depth' => $maxDepth,
        ];
    }

    /**
     * Reset alle Messungen
     */
    public function reset(): void
    {
        $this->callStack = [];
        $this->activeOperations = [];
        $this->completedOperations = [];
        $this->operationCounter = 0;

        $this->logger?->info('Nested performance tracker reset');
    }

    /**
     * Enable/Disable Tracking
     */
    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    /**
     * Generiere eindeutige Operation ID
     */
    private function generateOperationId(string $name): string
    {
        $this->operationCounter++;
        $sanitizedName = preg_replace('/[^a-zA-Z0-9_]/', '_', $name);

        return sprintf('%s_%d_%s', $sanitizedName, $this->operationCounter, substr(md5($name . microtime()), 0, 8));
    }

    /**
     * Entferne Operation vom Call Stack
     */
    private function removeFromCallStack(string $operationId): void
    {
        foreach ($this->callStack as $index => $operation) {
            if ($operation->operationId === $operationId) {
                array_splice($this->callStack, $index, 1);

                break;
            }
        }
    }

    /**
     * Finde Parent Operation in aktiven Operationen
     */
    private function findParentOperation(?string $parentId): ?NestedMeasurement
    {
        if (! $parentId) {
            return null;
        }

        return $this->activeOperations[$parentId] ?? null;
    }

    /**
     * Aktualisiere aktive Operation
     */
    private function updateActiveOperation(string $operationId, NestedMeasurement $updated): void
    {
        $this->activeOperations[$operationId] = $updated;

        // Auch im Call Stack aktualisieren
        foreach ($this->callStack as $index => $operation) {
            if ($operation->operationId === $operationId) {
                $this->callStack[$index] = $updated;

                break;
            }
        }
    }

    /**
     * Generate Flamegraph data in Brendan Gregg format
     *
     * Returns array of stack traces with sample counts for flamegraph visualization
     *
     * Format: ['stack_trace' => 'func1;func2;func3', 'samples' => duration_ms]
     *
     * @return array<int, array{stack_trace: string, samples: float}>
     */
    public function generateFlamegraph(): array
    {
        $flamegraphData = [];

        foreach ($this->completedOperations as $operation) {
            $flamegraphData = $this->collectFlamegraphData($operation, [], $flamegraphData);
        }

        return $flamegraphData;
    }

    /**
     * Generate Timeline data for visualization
     *
     * Returns array of timeline events with start/end times
     *
     * @return array<int, array{
     *     name: string,
     *     category: string,
     *     start_time: float,
     *     end_time: float,
     *     duration_ms: float,
     *     depth: int,
     *     operation_id: string,
     *     parent_id: string|null,
     *     self_time_ms: float,
     *     memory_delta_mb: float,
     *     context: array<string, mixed>
     * }>
     */
    public function generateTimeline(): array
    {
        $timeline = [];

        foreach ($this->completedOperations as $operation) {
            $timeline = $this->collectTimelineEvents($operation, $timeline);
        }

        // Sort by start time
        usort($timeline, fn($a, $b) => $a['start_time'] <=> $b['start_time']);

        return $timeline;
    }

    /**
     * Recursively collect flamegraph data from nested measurements
     */
    private function collectFlamegraphData(
        NestedMeasurement $measurement,
        array $parentStack,
        array $flamegraphData
    ): array {
        // Build stack trace
        $currentStack = array_merge($parentStack, [$measurement->name]);
        $stackTrace = implode(';', $currentStack);

        // Add to flamegraph data with duration as samples
        $samples = $measurement->getSelfTime()->toMilliseconds();
        if ($samples > 0) {
            $flamegraphData[] = [
                'stack_trace' => $stackTrace,
                'samples' => $samples,
            ];
        }

        // Recursively process children
        foreach ($measurement->children as $child) {
            $flamegraphData = $this->collectFlamegraphData($child, $currentStack, $flamegraphData);
        }

        return $flamegraphData;
    }

    /**
     * Recursively collect timeline events from nested measurements
     */
    private function collectTimelineEvents(
        NestedMeasurement $measurement,
        array $timeline
    ): array {
        if (!$measurement->duration || !$measurement->startTime || !$measurement->endTime) {
            return $timeline;
        }

        $timeline[] = [
            'name' => $measurement->name,
            'category' => $measurement->category->value,
            'start_time' => $measurement->startTime->toFloat(),
            'end_time' => $measurement->endTime->toFloat(),
            'duration_ms' => $measurement->duration->toMilliseconds(),
            'depth' => $measurement->depth,
            'operation_id' => $measurement->operationId,
            'parent_id' => $measurement->parentId,
            'self_time_ms' => $measurement->getSelfTime()->toMilliseconds(),
            'memory_delta_mb' => $measurement->getMemoryUsed()->toMegabytes(),
            'context' => $measurement->context,
        ];

        // Recursively process children
        foreach ($measurement->children as $child) {
            $timeline = $this->collectTimelineEvents($child, $timeline);
        }

        return $timeline;
    }

    /**
     * Debug info
     */
    public function getDebugInfo(): array
    {
        return [
            'enabled' => $this->enabled,
            'call_stack_depth' => count($this->callStack),
            'active_operations_count' => count($this->activeOperations),
            'completed_operations_count' => count($this->completedOperations),
            'operation_counter' => $this->operationCounter,
            'current_operation' => $this->getCurrentOperation()?->name,
        ];
    }
}
