<?php

declare(strict_types=1);

namespace App\Framework\Performance;

use App\Framework\Attributes\Singleton;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\HighResolutionClock;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\ValueObjects\Measurement;

/**
 * Enhanced performance collector with nanosecond-precision timing using HighResolutionClock
 */
#[Singleton]
final class EnhancedPerformanceCollector implements PerformanceCollectorInterface
{
    /** @var array<string, PerformanceMetric> */
    private array $metrics = [];

    /** @var array<string, array{start_time: Duration, start_memory: int, timestamp: Timestamp, parent?: string, depth: int}> */
    private array $activeTimers = [];

    /** @var string[] Call stack of active operation keys */
    private array $callStack = [];

    /** @var array<string, array{key: string, parent?: string, depth: int, children: string[], self_time?: float, total_time?: float}> */
    private array $nestedStructure = [];

    private bool $enabled;

    private Duration $requestStartNanos;

    #private Timestamp $requestStart;

    private int $requestMemoryStart;

    public function __construct(
        private readonly Clock $clock,
        private readonly HighResolutionClock $highResClock,
        private readonly MemoryMonitor $memoryMonitor,
        bool $enabled = false // Temporarily disabled to fix memory issues
    ) {
        $this->enabled = $enabled;

        if ($enabled) {
            $this->requestStartNanos = $this->highResClock->hrtime();
            // Record initial memory usage only if enabled
            $this->requestMemoryStart = $this->memoryMonitor->getCurrentMemory()->toBytes();
        } else {
            $this->requestStartNanos = Duration::zero();
            $this->requestMemoryStart = 0;
        }
    }

    public function startTiming(string $key, PerformanceCategory $category, array $context = []): void
    {
        if (! $this->enabled) {
            return;
        }

        // Get current parent from call stack
        $parent = end($this->callStack) ?: null;
        $depth = count($this->callStack);

        $this->activeTimers[$key] = [
            'start_time' => $this->highResClock->hrtime(),
            'start_memory' => $this->memoryMonitor->getCurrentMemory()->toBytes(),
            'timestamp' => $this->clock->time(),
            'parent' => $parent,
            'depth' => $depth,
        ];

        // Add to call stack
        $this->callStack[] = $key;

        // Initialize nested structure
        $this->nestedStructure[$key] = [
            'key' => $key,
            'parent' => $parent,
            'depth' => $depth,
            'children' => [],
        ];

        // Add as child to parent if exists
        if ($parent && isset($this->nestedStructure[$parent])) {
            $this->nestedStructure[$parent]['children'][] = $key;
        }

        if (! isset($this->metrics[$key])) {
            $this->metrics[$key] = PerformanceMetric::create($key, $category, array_merge($context, [
                'depth' => $depth,
                'parent' => $parent,
            ]));
        }
    }

    public function endTiming(string $key): void
    {
        if (! $this->enabled || ! isset($this->activeTimers[$key])) {
            return;
        }

        $startData = $this->activeTimers[$key];
        $measurement = Measurement::endHighResTiming(
            [
                'start_time' => $startData['start_time'],
                'start_memory' => $startData['start_memory'],
            ],
            $this->highResClock,
            $this->memoryMonitor,
            $this->clock
        );

        $totalTime = $measurement->getDuration()->toMilliseconds();

        // Calculate self time (total time minus children time)
        $childrenTime = $this->calculateChildrenTime($key);
        $selfTime = max(0, $totalTime - $childrenTime);

        // Update nested structure with timing info
        if (isset($this->nestedStructure[$key])) {
            $this->nestedStructure[$key]['total_time'] = $totalTime;
            $this->nestedStructure[$key]['self_time'] = $selfTime;
        }

        if (isset($this->metrics[$key])) {
            $this->metrics[$key]->addMeasurementObject($measurement);
        }

        // Remove from call stack
        $this->removeFromCallStack($key);

        unset($this->activeTimers[$key]);
    }

    public function measure(string $key, PerformanceCategory $category, callable $callback, array $context = []): mixed
    {
        if (! $this->enabled) {
            return $callback();
        }

        $this->startTiming($key, $category, $context);

        try {
            $result = $callback();
        } finally {
            $this->endTiming($key);
        }

        return $result;
    }

    public function recordMetric(string $key, PerformanceCategory $category, float $value, array $context = []): void
    {
        if (! $this->enabled) {
            return;
        }

        if (! isset($this->metrics[$key])) {
            $this->metrics[$key] = PerformanceMetric::create($key, $category, $context);
        }

        $this->metrics[$key]->addValue($value);
    }

    public function increment(string $key, PerformanceCategory $category, int $amount = 1, array $context = []): void
    {
        if (! $this->enabled) {
            return;
        }

        if (! isset($this->metrics[$key])) {
            $this->metrics[$key] = PerformanceMetric::create($key, $category, $context);
        }

        $this->metrics[$key]->increment($amount);
    }

    public function getMetrics(?PerformanceCategory $category = null): array
    {
        if ($category === null) {
            return $this->metrics;
        }

        return array_filter(
            $this->metrics,
            fn (PerformanceMetric $metric) => $metric->getCategory() === $category
        );
    }

    public function getMetric(string $key): ?PerformanceMetric
    {
        return $this->metrics[$key] ?? null;
    }

    public function getTotalRequestTime(): float
    {
        $currentTime = $this->highResClock->hrtime();
        $duration = $currentTime->subtract($this->requestStartNanos);

        return $duration->toMilliseconds();
    }

    public function getTotalRequestMemory(): int
    {
        $currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes();

        return max(0, $currentMemory - $this->requestMemoryStart);
    }

    public function getPeakMemory(): int
    {
        return $this->memoryMonitor->getPeakMemory()->toBytes();
    }

    public function reset(): void
    {
        $this->metrics = [];
        $this->activeTimers = [];
        $this->callStack = [];
        $this->nestedStructure = [];
        $this->requestStartNanos = $this->highResClock->hrtime();
        #$this->requestStart = Timestamp::fromClock($this->clock);
        $this->requestMemoryStart = $this->memoryMonitor->getCurrentMemory()->toBytes();
    }

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

    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    /**
     * Get all active timers (useful for debugging)
     */
    public function getActiveTimers(): array
    {
        return array_keys($this->activeTimers);
    }

    /**
     * Check if a timer is active
     */
    public function hasActiveTimer(string $key): bool
    {
        return isset($this->activeTimers[$key]);
    }

    /**
     * Benchmark a callable with nanosecond precision
     */
    public function benchmark(string $key, callable $callback, int $iterations = 1): array
    {
        if (! $this->enabled || $iterations < 1) {
            return [
                'total' => Duration::zero(),
                'average' => Duration::zero(),
                'min' => Duration::zero(),
                'max' => Duration::zero(),
                'result' => $iterations === 1 ? $callback() : null,
            ];
        }

        $results = $this->highResClock->benchmark($callback, $iterations);

        // Store as performance metric
        $category = PerformanceCategory::BENCHMARK;
        if (! isset($this->metrics[$key])) {
            $this->metrics[$key] = PerformanceMetric::create($key, $category, [
                'iterations' => $iterations,
                'benchmark' => true,
            ]);
        }

        // Add the average duration as a measurement
        $avgMeasurement = Measurement::create(
            $results['average'],
            Byte::fromBytes(0), // Benchmarks don't track memory by default
            $this->clock->time()
        );
        $this->metrics[$key]->addMeasurementObject($avgMeasurement);

        return $results;
    }

    /**
     * Measure duration of a callable with nanosecond precision
     */
    public function measureDuration(callable $callback): array
    {
        if (! $this->enabled) {
            return [
                'result' => $callback(),
                'duration' => Duration::zero(),
            ];
        }

        return $this->highResClock->measureDuration($callback);
    }

    /**
     * Get current high-resolution time as Duration
     */
    public function getHighResTime(): Duration
    {
        return $this->highResClock->hrtime();
    }

    /**
     * Get total request duration as Duration object with nanosecond precision
     */
    public function getTotalRequestDuration(): Duration
    {
        $currentTime = $this->highResClock->hrtime();

        return $currentTime->subtract($this->requestStartNanos);
    }

    // ========== NESTED MEASUREMENT FUNCTIONALITY ==========

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

    /**
     * Get current operation being measured
     */
    public function getCurrentOperation(): ?string
    {
        return end($this->callStack) ?: null;
    }

    /**
     * Get call stack
     */
    public function getCallStack(): array
    {
        return $this->callStack;
    }

    /**
     * Get nested structure with hierarchy information
     */
    public function getNestedStructure(): array
    {
        return $this->nestedStructure;
    }

    /**
     * Get hierarchical performance report
     */
    public function getHierarchicalReport(): array
    {
        $rootOperations = array_filter(
            $this->nestedStructure,
            fn ($structure) => $structure['parent'] === null
        );

        $hierarchicalReport = [];
        foreach ($rootOperations as $root) {
            $hierarchicalReport[] = $this->buildHierarchicalNode($root);
        }

        return [
            'operations' => $hierarchicalReport,
            'call_stack_depth' => $this->getCallStackDepth(),
            'active_operations' => count($this->activeTimers),
            'total_operations' => count($this->nestedStructure),
            'request_stats' => [
                'total_time_ms' => $this->getTotalRequestTime(),
                'total_memory_bytes' => $this->getTotalRequestMemory(),
                'peak_memory_bytes' => $this->getPeakMemory(),
            ],
        ];
    }

    /**
     * Get flat list of all operations with nested information
     */
    public function getFlatReport(): array
    {
        $flatOperations = [];

        foreach ($this->nestedStructure as $key => $structure) {
            $metric = $this->metrics[$key] ?? null;

            $flatOperations[] = [
                'key' => $key,
                'parent' => $structure['parent'],
                'depth' => $structure['depth'],
                'children_count' => count($structure['children']),
                'total_time_ms' => $structure['total_time'] ?? null,
                'self_time_ms' => $structure['self_time'] ?? null,
                'metric_data' => $metric?->toArray(),
                'is_active' => isset($this->activeTimers[$key]),
            ];
        }

        return $flatOperations;
    }

    /**
     * Get execution tree as string (for debugging)
     */
    public function getExecutionTree(): string
    {
        $tree = "=== Performance Execution Tree ===\n";

        if (! empty($this->callStack)) {
            $tree .= "Active Call Stack:\n";
            foreach ($this->callStack as $i => $key) {
                $structure = $this->nestedStructure[$key] ?? [];
                $indent = str_repeat('  ', $structure['depth'] ?? 0);
                $tree .= sprintf("%d. %s%s (running)\n", $i + 1, $indent, $key);
            }
            $tree .= "\n";
        }

        $rootOperations = array_filter(
            $this->nestedStructure,
            fn ($structure) => $structure['parent'] === null
        );

        if (! empty($rootOperations)) {
            $tree .= "Completed Operations:\n";
            foreach ($rootOperations as $root) {
                $tree .= $this->buildExecutionTreeNode($root);
            }
        }

        return $tree;
    }

    /**
     * Calculate total time spent in children of an operation
     */
    private function calculateChildrenTime(string $parentKey): float
    {
        if (! isset($this->nestedStructure[$parentKey])) {
            return 0.0;
        }

        $totalChildTime = 0.0;
        $children = $this->nestedStructure[$parentKey]['children'];

        foreach ($children as $childKey) {
            $childStructure = $this->nestedStructure[$childKey] ?? [];
            $totalChildTime += $childStructure['total_time'] ?? 0.0;
        }

        return $totalChildTime;
    }

    /**
     * Remove operation from call stack
     */
    private function removeFromCallStack(string $key): void
    {
        $index = array_search($key, $this->callStack, true);
        if ($index !== false) {
            array_splice($this->callStack, $index, 1);
        }
    }

    /**
     * Build hierarchical node for report
     */
    private function buildHierarchicalNode(array $structure): array
    {
        $node = [
            'key' => $structure['key'],
            'depth' => $structure['depth'],
            'total_time_ms' => $structure['total_time'] ?? null,
            'self_time_ms' => $structure['self_time'] ?? null,
            'children' => [],
        ];

        // Add metric data if available
        if (isset($this->metrics[$structure['key']])) {
            $node['metric_data'] = $this->metrics[$structure['key']]->toArray();
        }

        // Add children recursively
        foreach ($structure['children'] as $childKey) {
            if (isset($this->nestedStructure[$childKey])) {
                $node['children'][] = $this->buildHierarchicalNode($this->nestedStructure[$childKey]);
            }
        }

        return $node;
    }

    /**
     * Build execution tree node as string
     */
    private function buildExecutionTreeNode(array $structure, string $prefix = ''): string
    {
        $key = $structure['key'];
        $totalTime = $structure['total_time'] ?? 0;
        $selfTime = $structure['self_time'] ?? 0;
        $isActive = isset($this->activeTimers[$key]);

        $line = sprintf(
            "%s%s: %.2fms (self: %.2fms)%s\n",
            $prefix,
            $key,
            $totalTime,
            $selfTime,
            $isActive ? ' [ACTIVE]' : ''
        );

        // Add children
        foreach ($structure['children'] as $childKey) {
            if (isset($this->nestedStructure[$childKey])) {
                $line .= $this->buildExecutionTreeNode(
                    $this->nestedStructure[$childKey],
                    $prefix . '  '
                );
            }
        }

        return $line;
    }

    /**
     * Get performance summary with nested information
     */
    public function getNestedSummary(): array
    {
        $summary = [
            'total_operations' => count($this->nestedStructure),
            'active_operations' => count($this->activeTimers),
            'max_depth' => 0,
            'total_request_time_ms' => $this->getTotalRequestTime(),
        ];

        // Calculate max depth and other stats
        foreach ($this->nestedStructure as $structure) {
            $summary['max_depth'] = max($summary['max_depth'], $structure['depth']);
        }

        return $summary;
    }
}
