<?php

declare(strict_types=1);

use App\Framework\Performance\NestedPerformanceTracker;
use App\Framework\Performance\PerformanceCategory;
use App\Framework\DateTime\SystemClock;
use App\Framework\DateTime\SystemHighResolutionClock;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\Core\ValueObjects\Duration;

describe('NestedPerformanceTracker - Flamegraph Generation', function () {
    beforeEach(function () {
        $this->clock = new SystemClock();
        $this->highResClock = new SystemHighResolutionClock();
        $this->memoryMonitor = new MemoryMonitor();

        $this->tracker = new NestedPerformanceTracker(
            $this->clock,
            $this->highResClock,
            $this->memoryMonitor
        );
    });

    it('generates flamegraph data for single operation', function () {
        $this->tracker->measure(
            'database.query',
            PerformanceCategory::DATABASE,
            function () {
                usleep(10000); // 10ms
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(1);
        expect($flamegraph[0])->toHaveKeys(['stack_trace', 'samples']);
        expect($flamegraph[0]['stack_trace'])->toBe('database.query');
        expect($flamegraph[0]['samples'])->toBeGreaterThan(0);
    });

    it('generates flamegraph data for nested operations', function () {
        $this->tracker->measure(
            'http.request',
            PerformanceCategory::CONTROLLER,
            function () {
                usleep(5000); // 5ms

                $this->tracker->measure(
                    'database.query',
                    PerformanceCategory::DATABASE,
                    function () {
                        usleep(3000); // 3ms
                    }
                );

                usleep(2000); // 2ms
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(2);

        // Find parent operation
        $parentStack = array_values(array_filter(
            $flamegraph,
            fn($entry) => $entry['stack_trace'] === 'http.request'
        ));
        expect($parentStack)->toHaveCount(1);
        expect($parentStack[0]['samples'])->toBeGreaterThan(0);

        // Find child operation
        $childStack = array_values(array_filter(
            $flamegraph,
            fn($entry) => $entry['stack_trace'] === 'http.request;database.query'
        ));
        expect($childStack)->toHaveCount(1);
        expect($childStack[0]['samples'])->toBeGreaterThan(0);
    });

    it('generates flamegraph for deeply nested operations', function () {
        $this->tracker->measure(
            'controller.action',
            PerformanceCategory::CONTROLLER,
            function () {
                usleep(2000);

                $this->tracker->measure(
                    'service.process',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(2000);

                        $this->tracker->measure(
                            'repository.find',
                            PerformanceCategory::DATABASE,
                            function () {
                                usleep(2000);
                            }
                        );
                    }
                );
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(3);

        // Verify stack traces
        $stacks = array_column($flamegraph, 'stack_trace');
        expect($stacks)->toContain('controller.action');
        expect($stacks)->toContain('controller.action;service.process');
        expect($stacks)->toContain('controller.action;service.process;repository.find');
    });

    it('excludes operations with zero self-time', function () {
        $this->tracker->measure(
            'parent',
            PerformanceCategory::CUSTOM,
            function () {
                // No self-time, only child time
                $this->tracker->measure(
                    'child',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(5000);
                    }
                );
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        // Should only have child (parent has 0 self-time)
        expect($flamegraph)->toHaveCount(1);
        expect($flamegraph[0]['stack_trace'])->toBe('parent;child');
    });

    it('handles multiple parallel operations', function () {
        // First operation
        $this->tracker->measure(
            'operation.a',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(3000);
            }
        );

        // Second operation
        $this->tracker->measure(
            'operation.b',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(2000);
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(2);

        $stacks = array_column($flamegraph, 'stack_trace');
        expect($stacks)->toContain('operation.a');
        expect($stacks)->toContain('operation.b');
    });

    it('returns empty array when no operations completed', function () {
        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(0);
    });

    it('generates flamegraph with realistic component scenario', function () {
        $this->tracker->measure(
            'livecomponent.lifecycle',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(1000);

                $this->tracker->measure(
                    'livecomponent.resolve',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(2000);
                    }
                );

                $this->tracker->measure(
                    'livecomponent.render',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(3000);

                        $this->tracker->measure(
                            'template.process',
                            PerformanceCategory::VIEW,
                            function () {
                                usleep(1500);
                            }
                        );
                    }
                );

                usleep(500);
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toBeArray();
        expect($flamegraph)->toHaveCount(4);

        $stacks = array_column($flamegraph, 'stack_trace');
        expect($stacks)->toContain('livecomponent.lifecycle');
        expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.resolve');
        expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.render');
        expect($stacks)->toContain('livecomponent.lifecycle;livecomponent.render;template.process');
    });

    it('samples reflect actual duration', function () {
        $this->tracker->measure(
            'fast.operation',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(1000); // ~1ms
            }
        );

        $this->tracker->measure(
            'slow.operation',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(10000); // ~10ms
            }
        );

        $flamegraph = $this->tracker->generateFlamegraph();

        expect($flamegraph)->toHaveCount(2);

        $fast = array_values(array_filter(
            $flamegraph,
            fn($entry) => $entry['stack_trace'] === 'fast.operation'
        ))[0];

        $slow = array_values(array_filter(
            $flamegraph,
            fn($entry) => $entry['stack_trace'] === 'slow.operation'
        ))[0];

        // Slow operation should have more samples
        expect($slow['samples'])->toBeGreaterThan($fast['samples']);
    });
});

describe('NestedPerformanceTracker - Timeline Generation', function () {
    beforeEach(function () {
        $this->clock = new SystemClock();
        $this->highResClock = new SystemHighResolutionClock();
        $this->memoryMonitor = new MemoryMonitor();

        $this->tracker = new NestedPerformanceTracker(
            $this->clock,
            $this->highResClock,
            $this->memoryMonitor
        );
    });

    it('generates timeline for single operation', function () {
        $this->tracker->measure(
            'test.operation',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(5000);
            }
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toBeArray();
        expect($timeline)->toHaveCount(1);

        $event = $timeline[0];
        expect($event)->toHaveKeys([
            'name',
            'category',
            'start_time',
            'end_time',
            'duration_ms',
            'depth',
            'operation_id',
            'parent_id',
            'self_time_ms',
            'memory_delta_mb',
            'context'
        ]);

        expect($event['name'])->toBe('test.operation');
        expect($event['category'])->toBe('custom');
        expect($event['depth'])->toBe(0);
        expect($event['parent_id'])->toBeNull();
        expect($event['duration_ms'])->toBeGreaterThan(0);
    });

    it('generates timeline for nested operations', function () {
        $this->tracker->measure(
            'parent',
            PerformanceCategory::CONTROLLER,
            function () {
                usleep(2000);

                $this->tracker->measure(
                    'child',
                    PerformanceCategory::DATABASE,
                    function () {
                        usleep(3000);
                    }
                );
            }
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toBeArray();
        expect($timeline)->toHaveCount(2);

        // Check parent event
        $parent = $timeline[0];
        expect($parent['name'])->toBe('parent');
        expect($parent['depth'])->toBe(0);
        expect($parent['parent_id'])->toBeNull();

        // Check child event
        $child = $timeline[1];
        expect($child['name'])->toBe('child');
        expect($child['depth'])->toBe(1);
        expect($child['parent_id'])->not->toBeNull();
        expect($child['parent_id'])->toBe($parent['operation_id']);
    });

    it('sorts timeline events by start time', function () {
        // Create operations in specific order
        $this->tracker->measure(
            'operation.1',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(1000);
            }
        );

        usleep(500); // Small gap

        $this->tracker->measure(
            'operation.2',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(1000);
            }
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toHaveCount(2);

        // Verify chronological order
        expect($timeline[0]['name'])->toBe('operation.1');
        expect($timeline[1]['name'])->toBe('operation.2');
        expect($timeline[0]['start_time'])->toBeLessThan($timeline[1]['start_time']);
    });

    it('includes memory delta in timeline', function () {
        $this->tracker->measure(
            'memory.test',
            PerformanceCategory::CUSTOM,
            function () {
                // Allocate some memory
                $data = array_fill(0, 10000, 'test');
                usleep(1000);
            }
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toHaveCount(1);
        expect($timeline[0]['memory_delta_mb'])->toBeFloat();
    });

    it('includes operation context in timeline', function () {
        $this->tracker->measure(
            'contextual.operation',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(1000);
            },
            ['user_id' => 123, 'action' => 'test']
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toHaveCount(1);
        expect($timeline[0]['context'])->toHaveKeys(['user_id', 'action']);
        expect($timeline[0]['context']['user_id'])->toBe(123);
        expect($timeline[0]['context']['action'])->toBe('test');
    });

    it('calculates self-time correctly', function () {
        $this->tracker->measure(
            'parent',
            PerformanceCategory::CUSTOM,
            function () {
                usleep(5000); // Self time: ~5ms

                $this->tracker->measure(
                    'child',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(3000); // Child time: ~3ms
                    }
                );
            }
        );

        $timeline = $this->tracker->generateTimeline();

        $parent = $timeline[0];
        $child = $timeline[1];

        // Parent total duration should be > child duration
        expect($parent['duration_ms'])->toBeGreaterThan($child['duration_ms']);

        // Parent self-time should be less than total duration
        expect($parent['self_time_ms'])->toBeLessThan($parent['duration_ms']);

        // Child self-time should equal its duration (no sub-operations)
        expect($child['self_time_ms'])->toBe($child['duration_ms']);
    });

    it('handles empty tracker gracefully', function () {
        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toBeArray();
        expect($timeline)->toHaveCount(0);
    });

    it('generates complete timeline for component lifecycle', function () {
        $this->tracker->measure(
            'livecomponent.lifecycle',
            PerformanceCategory::CUSTOM,
            function () {
                $this->tracker->measure(
                    'livecomponent.resolve',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(2000);
                    },
                    ['phase' => 'initialization']
                );

                $this->tracker->measure(
                    'livecomponent.render',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(3000);
                    },
                    ['cached' => false]
                );

                $this->tracker->measure(
                    'livecomponent.handle',
                    PerformanceCategory::CUSTOM,
                    function () {
                        usleep(1500);
                    },
                    ['action' => 'submit']
                );
            },
            ['component_id' => 'user-profile']
        );

        $timeline = $this->tracker->generateTimeline();

        expect($timeline)->toHaveCount(4);

        // Verify all phases present
        $names = array_column($timeline, 'name');
        expect($names)->toContain('livecomponent.lifecycle');
        expect($names)->toContain('livecomponent.resolve');
        expect($names)->toContain('livecomponent.render');
        expect($names)->toContain('livecomponent.handle');

        // Verify context propagation
        $lifecycle = $timeline[0];
        expect($lifecycle['context'])->toHaveKey('component_id');
        expect($lifecycle['context']['component_id'])->toBe('user-profile');
    });
});
