<?php

declare(strict_types=1);

use App\Framework\LiveComponents\Tracing\LiveComponentTracer;
use App\Framework\Telemetry\UnifiedTelemetryService;
use App\Framework\Telemetry\OperationHandle;
use App\Framework\Telemetry\Config\TelemetryConfig;
use App\Framework\Tracing\TraceSpan;
use App\Framework\Tracing\SpanStatus;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\CircuitBreaker\CircuitBreaker;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Serializer\Serializer;
use App\Framework\Logging\DefaultLogger;

describe('LiveComponentTracer', function () {
    beforeEach(function () {
        // Create telemetry service with enabled configuration
        $this->config = new TelemetryConfig(
            serviceName: 'test-service',
            serviceVersion: '1.0.0',
            environment: 'test',
            enabled: true
        );

        // Create real instances for final classes
        $this->clock = new SystemClock();
        $serializer = Mockery::mock(Serializer::class);
        $serializer->shouldReceive('serialize')->andReturnUsing(fn($val) => serialize($val));
        $serializer->shouldReceive('unserialize')->andReturnUsing(fn($val) => unserialize($val));
        $this->cache = new GeneralCache(new InMemoryCache(), $serializer);

        // Create CircuitBreaker with test dependencies
        $this->circuitBreaker = new CircuitBreaker($this->cache, $this->clock, null, null, null, 'test');

        // Mock interfaces
        $this->performanceCollector = Mockery::mock(PerformanceCollectorInterface::class);

        // Configure mocks
        $this->performanceCollector->shouldReceive('startTiming')->andReturnNull();
        $this->performanceCollector->shouldReceive('endTiming')->andReturnNull();

        // Create simple logger for tests
        $this->logger = new DefaultLogger();

        $this->telemetry = new UnifiedTelemetryService(
            $this->performanceCollector,
            $this->circuitBreaker,
            $this->logger,
            $this->clock,
            $this->config
        );

        $this->tracer = new LiveComponentTracer($this->telemetry);
    });

    afterEach(function () {
        Mockery::close();
    });

    describe('Resolve Operation Tracing', function () {
        it('creates trace span for component resolution', function () {
            $operation = $this->tracer->traceResolve('test-component', [
                'data_provider' => 'UserProvider',
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            // End the operation
            $operation->end('success');
        });

        it('includes component id in resolve attributes', function () {
            $operation = $this->tracer->traceResolve('user-list', [
                'initial_data' => ['page' => 1],
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('handles resolve operation errors gracefully', function () {
            $operation = $this->tracer->traceResolve('failing-component');

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            // End with error
            $operation->fail('Component resolution failed');
        });
    });

    describe('Render Operation Tracing', function () {
        it('creates trace span for component rendering', function () {
            $operation = $this->tracer->traceRender('test-component', [
                'cached' => false,
                'template' => 'components/user-card',
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('tracks render operation with view type', function () {
            $operation = $this->tracer->traceRender('product-list', [
                'items_count' => 50,
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('handles render failures with error status', function () {
            $operation = $this->tracer->traceRender('broken-template');

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->fail('Template rendering failed');
        });
    });

    describe('Handle Operation Tracing', function () {
        it('creates trace span for action handling', function () {
            $operation = $this->tracer->traceHandle(
                'form-component',
                'submitForm',
                ['validation' => 'passed']
            );

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('includes action name in handle attributes', function () {
            $operation = $this->tracer->traceHandle(
                'cart-component',
                'addItem',
                ['item_id' => 123]
            );

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('tracks action execution time', function () {
            $operation = $this->tracer->traceHandle('data-table', 'sortColumn');

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            // Simulate some work
            usleep(1000); // 1ms

            $operation->end('success');
        });
    });

    describe('Upload Operation Tracing', function () {
        it('creates trace span for file upload', function () {
            $operation = $this->tracer->traceUpload(
                'file-uploader',
                'upload-session-123',
                ['chunk' => 1, 'total_chunks' => 5]
            );

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('includes upload session id in attributes', function () {
            $operation = $this->tracer->traceUpload(
                'avatar-upload',
                'session-abc-def',
                ['file_size' => 2048000]
            );

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('handles upload failures', function () {
            $operation = $this->tracer->traceUpload('doc-uploader', 'session-xyz');

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->fail('Upload failed: file too large');
        });
    });

    describe('Event Recording', function () {
        it('records component lifecycle events', function () {
            // Events are fire-and-forget, no return value
            $this->tracer->recordEvent('mounted', 'test-component', [
                'mount_time_ms' => 45.2,
            ]);

            // Should not throw
            expect(true)->toBeTrue();
        });

        it('records multiple events for component', function () {
            $componentId = 'lifecycle-test';

            $this->tracer->recordEvent('mounted', $componentId);
            $this->tracer->recordEvent('updated', $componentId, ['updates' => 3]);
            $this->tracer->recordEvent('destroyed', $componentId);

            expect(true)->toBeTrue();
        });

        it('records custom component events', function () {
            $this->tracer->recordEvent('form_submitted', 'contact-form', [
                'fields_count' => 5,
                'validation_passed' => true,
            ]);

            expect(true)->toBeTrue();
        });
    });

    describe('Metric Recording', function () {
        it('records component metrics', function () {
            $this->tracer->recordMetric('render_duration', 25.5, 'ms', [
                'component_id' => 'test-component',
            ]);

            expect(true)->toBeTrue();
        });

        it('records multiple metrics with different units', function () {
            $this->tracer->recordMetric('cache_size', 1024, 'bytes');
            $this->tracer->recordMetric('action_count', 15, 'count');
            $this->tracer->recordMetric('response_time', 150.3, 'ms');

            expect(true)->toBeTrue();
        });

        it('records metrics without unit', function () {
            $this->tracer->recordMetric('items_rendered', 50.0);

            expect(true)->toBeTrue();
        });
    });

    describe('Traced Execution', function () {
        it('executes callback within traced operation', function () {
            $result = $this->tracer->trace(
                'resolve',
                'test-component',
                fn() => ['data' => 'loaded'],
                ['source' => 'database']
            );

            expect($result)->toBe(['data' => 'loaded']);
        });

        it('executes render operation with trace', function () {
            $html = $this->tracer->trace(
                'render',
                'user-card',
                fn() => '<div>User Card</div>'
            );

            expect($html)->toBe('<div>User Card</div>');
        });

        it('executes handle operation with trace', function () {
            $result = $this->tracer->trace(
                'handle',
                'form-component',
                fn() => ['success' => true, 'message' => 'Form submitted']
            );

            expect($result['success'])->toBeTrue();
            expect($result['message'])->toBe('Form submitted');
        });

        it('executes upload operation with trace', function () {
            $uploadResult = $this->tracer->trace(
                'upload',
                'file-uploader',
                fn() => ['uploaded' => true, 'file_id' => 123]
            );

            expect($uploadResult['uploaded'])->toBeTrue();
            expect($uploadResult['file_id'])->toBe(123);
        });

        it('propagates exceptions from traced callbacks', function () {
            expect(fn() => $this->tracer->trace(
                'render',
                'broken-component',
                fn() => throw new \RuntimeException('Template not found')
            ))->toThrow(\RuntimeException::class, 'Template not found');
        });
    });

    describe('Complex Tracing Scenarios', function () {
        it('handles nested tracing operations', function () {
            $result = $this->tracer->trace('resolve', 'parent-component', function() {
                // Nested render operation
                return $this->tracer->trace('render', 'child-component', function() {
                    // Nested action handling
                    return $this->tracer->trace('handle', 'nested-action', function() {
                        return ['nested' => true];
                    });
                });
            });

            expect($result['nested'])->toBeTrue();
        });

        it('traces complete component lifecycle', function () {
            $componentId = 'full-lifecycle';

            // 1. Resolve
            $resolveOp = $this->tracer->traceResolve($componentId);
            $resolveOp->end('success');

            // 2. Render
            $renderOp = $this->tracer->traceRender($componentId);
            $renderOp->end('success');

            // 3. Handle action
            $handleOp = $this->tracer->traceHandle($componentId, 'onClick');
            $handleOp->end('success');

            // 4. Re-render
            $rerenderOp = $this->tracer->traceRender($componentId);
            $rerenderOp->end('success');

            expect(true)->toBeTrue();
        });

        it('traces concurrent operations on different components', function () {
            $op1 = $this->tracer->traceRender('component-1');
            $op2 = $this->tracer->traceRender('component-2');
            $op3 = $this->tracer->traceHandle('component-3', 'action');

            // End in different order
            $op2->end('success');
            $op1->end('success');
            $op3->end('success');

            expect(true)->toBeTrue();
        });
    });

    describe('Telemetry Integration', function () {
        it('checks if telemetry is enabled', function () {
            expect($this->tracer->isEnabled())->toBeTrue();
        });

        it('provides operation stack for debugging', function () {
            $this->tracer->trace('resolve', 'test-component', function() {
                $stack = $this->tracer->getOperationStack();
                expect($stack)->toBeString();
            });
        });

        it('handles telemetry disabled gracefully', function () {
            // Create tracer with disabled telemetry
            $disabledConfig = new TelemetryConfig(
                serviceName: 'test-service',
                serviceVersion: '1.0.0',
                environment: 'test',
                enabled: false
            );
            $disabledTelemetry = new UnifiedTelemetryService(
                $this->performanceCollector,
                $this->circuitBreaker,
                $this->logger,
                $this->clock,
                $disabledConfig
            );
            $disabledTracer = new LiveComponentTracer($disabledTelemetry);

            expect($disabledTracer->isEnabled())->toBeFalse();

            // Operations should still work without throwing
            $op = $disabledTracer->traceResolve('test-component');
            $op->end('success');

            expect(true)->toBeTrue();
        });
    });

    describe('Performance Characteristics', function () {
        it('has minimal overhead for tracing', function () {
            $start = microtime(true);

            for ($i = 0; $i < 100; $i++) {
                $op = $this->tracer->traceRender("component-{$i}");
                $op->end('success');
            }

            $duration = (microtime(true) - $start) * 1000; // ms

            // 100 operations should complete in reasonable time
            expect($duration)->toBeLessThan(100.0); // < 1ms per operation
        });

        it('handles high-frequency event recording', function () {
            $start = microtime(true);

            for ($i = 0; $i < 1000; $i++) {
                $this->tracer->recordEvent('click', 'button-component');
            }

            $duration = (microtime(true) - $start) * 1000; // ms

            // 1000 events should record quickly
            expect($duration)->toBeLessThan(200.0); // < 0.2ms per event
        });
    });

    describe('Error Handling', function () {
        it('records operation failures correctly', function () {
            $operation = $this->tracer->traceHandle('error-component', 'failingAction');

            // Simulate failure
            $operation->fail('Action failed: validation error');

            expect(true)->toBeTrue();
        });

        it('continues tracing after operation errors', function () {
            // First operation fails
            $op1 = $this->tracer->traceResolve('failing-component');
            $op1->fail('Resolution failed');

            // Subsequent operations should still work
            $op2 = $this->tracer->traceRender('working-component');
            $op2->end('success');

            expect(true)->toBeTrue();
        });

        it('handles exceptions in traced callbacks', function () {
            try {
                $this->tracer->trace('handle', 'exception-component', function() {
                    throw new \RuntimeException('Business logic error');
                });
            } catch (\RuntimeException $e) {
                expect($e->getMessage())->toBe('Business logic error');
            }

            // Tracing should still work after exception
            $op = $this->tracer->traceRender('recovery-component');
            $op->end('success');

            expect(true)->toBeTrue();
        });
    });

    describe('Attributes and Context', function () {
        it('merges custom attributes with operation attributes', function () {
            $operation = $this->tracer->traceResolve('test-component', [
                'custom_attr1' => 'value1',
                'custom_attr2' => 123,
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('preserves attribute types', function () {
            $operation = $this->tracer->traceHandle('type-test', 'action', [
                'string_attr' => 'text',
                'int_attr' => 42,
                'float_attr' => 3.14,
                'bool_attr' => true,
                'array_attr' => ['a', 'b', 'c'],
            ]);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });

        it('handles empty attributes gracefully', function () {
            $operation = $this->tracer->traceRender('minimal-component', []);

            expect($operation)->toBeInstanceOf(OperationHandle::class);

            $operation->end('success');
        });
    });
});
