<?php

declare(strict_types=1);

use App\Framework\LiveComponents\Profiling\ProfileTimeline;
use App\Framework\LiveComponents\Profiling\ValueObjects\ProfileResult;
use App\Framework\LiveComponents\Profiling\ValueObjects\ProfileSessionId;
use App\Framework\LiveComponents\Profiling\ValueObjects\ProfilePhase;
use App\Framework\LiveComponents\Profiling\ValueObjects\MemorySnapshot;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Timestamp;

describe('ProfileTimeline', function () {
    beforeEach(function () {
        $this->timeline = new ProfileTimeline();

        // Create test ProfileResult
        $this->result = new ProfileResult(
            sessionId: ProfileSessionId::generate('UserCard'),
            componentId: 'UserCard',
            totalDuration: Duration::fromMilliseconds(50.0),
            totalMemory: Byte::fromBytes(4096),
            phases: [
                ProfilePhase::create('resolve', 10.0, 1024, ['success' => true]),
                ProfilePhase::create('render', 30.0, 2048, ['cached' => false, 'success' => true]),
                ProfilePhase::create('action.submit', 10.0, 1024, ['success' => true]),
            ],
            memorySnapshots: [
                new MemorySnapshot(
                    label: 'after_resolve',
                    currentUsage: Byte::fromBytes(1048576),
                    peakUsage: Byte::fromBytes(1048576),
                    allocatedObjects: 100,
                    timestamp: Timestamp::now()
                ),
                new MemorySnapshot(
                    label: 'after_render',
                    currentUsage: Byte::fromBytes(2097152),
                    peakUsage: Byte::fromBytes(2097152),
                    allocatedObjects: 150,
                    timestamp: Timestamp::now()
                ),
            ],
            startTime: Timestamp::now(),
            endTime: Timestamp::now()
        );
    });

    it('generates DevTools timeline', function () {
        $timeline = $this->timeline->generateDevToolsTimeline($this->result);

        expect($timeline)->toBeArray();
        expect($timeline)->not->toBeEmpty();

        // Should have metadata event
        $metadataEvent = $timeline[0];
        expect($metadataEvent['ph'])->toBe('M'); // Metadata
        expect($metadataEvent['name'])->toBe('thread_name');

        // Should have complete events for phases
        $completeEvents = array_filter($timeline, fn($e) => $e['ph'] === 'X');
        expect($completeEvents)->toHaveCount(3); // 3 phases

        // Should have instant events for memory snapshots
        $instantEvents = array_filter($timeline, fn($e) => $e['ph'] === 'i');
        expect($instantEvents)->toHaveCount(2); // 2 snapshots
    });

    it('generates simple timeline', function () {
        $timeline = $this->timeline->generateSimpleTimeline($this->result);

        expect($timeline)->toBeArray();
        expect($timeline)->toHaveKeys([
            'component_id',
            'session_id',
            'total_duration_ms',
            'total_memory_mb',
            'start_time',
            'end_time',
            'phases',
            'memory_snapshots',
        ]);

        expect($timeline['component_id'])->toBe('UserCard');
        expect($timeline['phases'])->toHaveCount(3);
        expect($timeline['memory_snapshots'])->toHaveCount(2);

        // Verify phase structure
        $firstPhase = $timeline['phases'][0];
        expect($firstPhase)->toHaveKeys([
            'name',
            'start_ms',
            'end_ms',
            'duration_ms',
            'memory_mb',
            'success',
            'error',
        ]);
    });

    it('generates flamegraph data', function () {
        $flamegraph = $this->timeline->generateFlamegraph($this->result);

        expect($flamegraph)->toBeString();
        expect($flamegraph)->toContain('livecomponent.UserCard;resolve');
        expect($flamegraph)->toContain('livecomponent.UserCard;render');
        expect($flamegraph)->toContain('livecomponent.UserCard;action.submit');

        // Should have stack format: "stack_name count"
        $lines = explode("\n", $flamegraph);
        foreach ($lines as $line) {
            if (!empty($line)) {
                expect($line)->toMatch('/^[^;]+;[^\s]+ \d+$/');
            }
        }
    });

    it('generates Gantt chart data', function () {
        $gantt = $this->timeline->generateGanttChart($this->result);

        expect($gantt)->toBeArray();
        expect($gantt)->toHaveKeys(['component_id', 'total_duration_ms', 'tasks']);
        expect($gantt['tasks'])->toHaveCount(3);

        // Verify task structure
        $firstTask = $gantt['tasks'][0];
        expect($firstTask)->toHaveKeys([
            'id',
            'name',
            'start',
            'end',
            'duration',
            'memory',
            'success',
            'type',
        ]);

        expect($firstTask['name'])->toBe('Resolve');
        expect($firstTask['type'])->toBe('initialization');
    });

    it('generates waterfall diagram', function () {
        $waterfall = $this->timeline->generateWaterfall($this->result);

        expect($waterfall)->toBeArray();
        expect($waterfall)->toHaveKeys(['component_id', 'total_time', 'entries']);
        expect($waterfall['entries'])->toHaveCount(3);

        // Verify entry structure
        $firstEntry = $waterfall['entries'][0];
        expect($firstEntry)->toHaveKeys([
            'name',
            'start_time',
            'duration',
            'memory_delta',
            'timing',
            'status',
        ]);

        expect($firstEntry['timing'])->toHaveKeys([
            'blocked',
            'execution',
            'total',
        ]);
    });

    it('categorizes phases by type', function () {
        $gantt = $this->timeline->generateGanttChart($this->result);

        $resolveTask = $gantt['tasks'][0];
        $renderTask = $gantt['tasks'][1];
        $actionTask = $gantt['tasks'][2];

        expect($resolveTask['type'])->toBe('initialization');
        expect($renderTask['type'])->toBe('rendering');
        expect($actionTask['type'])->toBe('interaction');
    });

    it('exports as JSON in different formats', function () {
        $formats = ['devtools', 'simple', 'gantt', 'waterfall'];

        foreach ($formats as $format) {
            $json = $this->timeline->exportAsJson($this->result, $format);

            expect($json)->toBeString();

            $decoded = json_decode($json, true);
            expect($decoded)->not->toBeNull();
            expect(json_last_error())->toBe(JSON_ERROR_NONE);
        }
    });

    it('throws exception for unknown format', function () {
        expect(fn() => $this->timeline->exportAsJson($this->result, 'unknown'))
            ->toThrow(\InvalidArgumentException::class);
    });

    it('includes error stacks in flamegraph for failed phases', function () {
        $resultWithError = new ProfileResult(
            sessionId: ProfileSessionId::generate('UserCard'),
            componentId: 'UserCard',
            totalDuration: Duration::fromMilliseconds(20.0),
            totalMemory: Byte::fromBytes(2048),
            phases: [
                ProfilePhase::create('render', 20.0, 2048, [
                    'success' => false,
                    'error' => 'Render failed'
                ]),
            ],
            memorySnapshots: [],
            startTime: Timestamp::now(),
            endTime: Timestamp::now()
        );

        $flamegraph = $this->timeline->generateFlamegraph($resultWithError);

        expect($flamegraph)->toContain('livecomponent.UserCard;render');
        expect($flamegraph)->toContain('livecomponent.UserCard;render;error');
    });

    it('generates counter events for memory tracking in DevTools format', function () {
        $timeline = $this->timeline->generateDevToolsTimeline($this->result);

        $counterEvents = array_filter($timeline, fn($e) => $e['ph'] === 'C');
        expect(count($counterEvents))->toBeGreaterThan(0);

        foreach ($counterEvents as $event) {
            expect($event)->toHaveKey('args');
            expect($event['args'])->toHaveKey('memory_mb');
        }
    });

    it('preserves phase timing in simple timeline', function () {
        $timeline = $this->timeline->generateSimpleTimeline($this->result);

        $phases = $timeline['phases'];

        // First phase starts at 0
        expect($phases[0]['start_ms'])->toBe(0.0);
        expect($phases[0]['end_ms'])->toBe(10.0);

        // Second phase starts where first ended
        expect($phases[1]['start_ms'])->toBe(10.0);
        expect($phases[1]['end_ms'])->toBe(40.0);

        // Third phase starts where second ended
        expect($phases[2]['start_ms'])->toBe(40.0);
        expect($phases[2]['end_ms'])->toBe(50.0);
    });
});
