<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\View\Processors;

use App\Framework\LiveComponents\Contracts\ComponentRegistryInterface;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\Performance\CompiledComponentMetadata;
use App\Framework\LiveComponents\Performance\ComponentMetadataCacheInterface;
use App\Framework\LiveComponents\Performance\ComponentPropertyMetadata;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
use App\Framework\Meta\MetaData;
use App\Framework\Template\Parser\DomTemplateParser;
use App\Framework\View\Contracts\HtmlComponentRegistryInterface;
use App\Framework\View\DomComponentService;
use App\Framework\View\Processors\XComponentProcessor;
use App\Framework\View\RenderContext;

describe('XComponentProcessor', function () {
    beforeEach(function () {
        // Mock dependencies via Interfaces
        $this->liveComponentRegistry = mock(ComponentRegistryInterface::class);
        $this->htmlComponentRegistry = mock(HtmlComponentRegistryInterface::class);
        $this->metadataCache = mock(ComponentMetadataCacheInterface::class);
        $this->componentService = new DomComponentService();

        $this->processor = new XComponentProcessor(
            $this->liveComponentRegistry,
            $this->htmlComponentRegistry,
            $this->metadataCache,
            $this->componentService
        );

        $this->parser = new DomTemplateParser();
    });

    describe('LiveComponent Processing', function () {
        it('processes LiveComponent with basic props', function () {
            $html = '<html><body><x-counter id="demo" initialValue="5" /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            // Mock LiveComponent
            $mockComponent = mock(LiveComponentContract::class);
            $mockComponent->shouldReceive('getId')
                ->andReturn(ComponentId::create('counter', 'demo'));
            $mockComponent->shouldReceive('getData')
                ->andReturn(ComponentData::fromArray(['initialValue' => 5]));
            $mockComponent->shouldReceive('getRenderData')
                ->andReturn(new ComponentRenderData('counter-template', ['value' => 5]));

            // Setup registry mocks
            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('counter')
                ->andReturn(true);

            $this->liveComponentRegistry->shouldReceive('getClassName')
                ->with('counter')
                ->andReturn('TestCounterComponent');

            $this->liveComponentRegistry->shouldReceive('resolve')
                ->once()
                ->andReturn($mockComponent);

            $this->liveComponentRegistry->shouldReceive('renderWithWrapper')
                ->with($mockComponent)
                ->andReturn('<div data-component-id="counter:demo">Counter HTML</div>');

            // Mock metadata for validation
            $mockMetadata = new CompiledComponentMetadata(
                className: 'TestCounterComponent',
                componentName: 'counter',
                properties: [
                    'initialValue' => new ComponentPropertyMetadata(
                        name: 'initialValue',
                        type: 'int',
                        isPublic: true,
                        isReadonly: false
                    )
                ],
                actions: [],
                constructorParams: []
            );
            $this->metadataCache->shouldReceive('get')
                ->with('TestCounterComponent')
                ->andReturn($mockMetadata);

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            // Assert
            $html = $result->document->saveHTML();
            expect($html)->toContain('data-component-id="counter:demo"');
            expect($html)->toContain('Counter HTML');
        });

        it('coerces prop types correctly', function () {
            $html = '<html><body><x-test
                stringProp="text"
                intProp="123"
                floatProp="12.5"
                boolTrue="true"
                boolFalse="false"
                nullProp="null"
                arrayProp="[1,2,3]"
            /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            $capturedProps = null;

            $mockComponent = mock(LiveComponentContract::class);
            $mockComponent->shouldReceive('getId')
                ->andReturn(ComponentId::create('test', 'test-auto'));
            $mockComponent->shouldReceive('getData')
                ->andReturn(ComponentData::fromArray([]));
            $mockComponent->shouldReceive('getRenderData')
                ->andReturn(new ComponentRenderData('test-template', []));

            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('test')
                ->andReturn(true);

            $this->liveComponentRegistry->shouldReceive('getClassName')
                ->with('test')
                ->andReturn('TestComponent');

            $this->liveComponentRegistry->shouldReceive('resolve')
                ->with(\Mockery::type(ComponentId::class), \Mockery::on(function ($data) use (&$capturedProps) {
                    $capturedProps = $data->toArray();

                    return true;
                }))
                ->andReturn($mockComponent);

            $this->liveComponentRegistry->shouldReceive('renderWithWrapper')
                ->andReturn('<div>Test</div>');

            // Mock metadata - accept all props
            $mockMetadata = new CompiledComponentMetadata(
                className: 'TestComponent',
                componentName: 'test',
                properties: [
                    'stringProp' => new ComponentPropertyMetadata('stringProp', 'string', true, false),
                    'intProp' => new ComponentPropertyMetadata('intProp', 'int', true, false),
                    'floatProp' => new ComponentPropertyMetadata('floatProp', 'float', true, false),
                    'boolTrue' => new ComponentPropertyMetadata('boolTrue', 'bool', true, false),
                    'boolFalse' => new ComponentPropertyMetadata('boolFalse', 'bool', true, false),
                    'nullProp' => new ComponentPropertyMetadata('nullProp', 'mixed', true, false),
                    'arrayProp' => new ComponentPropertyMetadata('arrayProp', 'array', true, false),
                ],
                actions: [],
                constructorParams: []
            );
            $this->metadataCache->shouldReceive('get')
                ->andReturn($mockMetadata);

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $this->processor->process($dom, $context);

            // Assert type coercion
            expect($capturedProps['stringProp'])->toBe('text');
            expect($capturedProps['intProp'])->toBe(123);
            expect($capturedProps['floatProp'])->toBe(12.5);
            expect($capturedProps['boolTrue'])->toBeTrue();
            expect($capturedProps['boolFalse'])->toBeFalse();
            expect($capturedProps['nullProp'])->toBeNull();
            expect($capturedProps['arrayProp'])->toBe([1, 2, 3]);
        });

        it('validates props against ComponentMetadata', function () {
            $html = '<html><body><x-counter id="demo" invalidProp="test" /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('counter')
                ->andReturn(true);

            $this->liveComponentRegistry->shouldReceive('getClassName')
                ->with('counter')
                ->andReturn('TestCounterComponent');

            // Mock metadata without invalidProp
            $mockMetadata = new CompiledComponentMetadata(
                className: 'TestCounterComponent',
                componentName: 'counter',
                properties: [
                    'initialValue' => new ComponentPropertyMetadata(
                        name: 'initialValue',
                        type: 'int',
                        isPublic: true,
                        isReadonly: false
                    )
                ],
                actions: [],
                constructorParams: []
            );
            $this->metadataCache->shouldReceive('get')
                ->with('TestCounterComponent')
                ->andReturn($mockMetadata);

            // Process - should handle error gracefully in dev mode
            $_ENV['APP_ENV'] = 'development';
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            // Should contain error message in development
            $html = $result->document->saveHTML();
            expect($html)->toContain('XComponentProcessor Error');
        });

        it('generates unique ID if not provided', function () {
            $html = '<html><body><x-counter initialValue="0" /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            $mockComponent = mock(LiveComponentContract::class);
            $mockComponent->shouldReceive('getId')
                ->andReturn(ComponentId::create('counter', 'counter-auto'));
            $mockComponent->shouldReceive('getData')
                ->andReturn(ComponentData::fromArray([]));
            $mockComponent->shouldReceive('getRenderData')
                ->andReturn(new ComponentRenderData('test', []));

            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->andReturn(true);

            $this->liveComponentRegistry->shouldReceive('getClassName')
                ->andReturn('TestComponent');

            $this->liveComponentRegistry->shouldReceive('resolve')
                ->with(\Mockery::on(function ($id) {
                    // Should have auto-generated ID
                    return str_starts_with($id->toString(), 'counter:counter-');
                }), \Mockery::any())
                ->andReturn($mockComponent);

            $this->liveComponentRegistry->shouldReceive('renderWithWrapper')
                ->andReturn('<div>Counter</div>');

            $mockMetadata = new CompiledComponentMetadata(
                className: 'TestComponent',
                componentName: 'counter',
                properties: [
                    'initialValue' => new ComponentPropertyMetadata(
                        name: 'initialValue',
                        type: 'int',
                        isPublic: true,
                        isReadonly: false
                    )
                ],
                actions: [],
                constructorParams: []
            );
            $this->metadataCache->shouldReceive('get')->andReturn($mockMetadata);

            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $this->processor->process($dom, $context);

            // Assertion in mock expectations above
            expect(true)->toBeTrue();
        });
    });

    describe('HTML Component Processing', function () {
        it('processes HTML Component', function () {
            $html = '<html><body><x-button variant="primary">Click me</x-button></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            // LiveComponent not registered
            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('button')
                ->andReturn(false);

            // HTML Component registered
            $this->htmlComponentRegistry->shouldReceive('has')
                ->with('button')
                ->andReturn(true);

            $this->htmlComponentRegistry->shouldReceive('render')
                ->with('button', 'Click me', ['variant' => 'primary'])
                ->andReturn('<button class="btn btn-primary">Click me</button>');

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            // Assert
            $html = $result->document->saveHTML();
            expect($html)->toContain('<button class="btn btn-primary">Click me</button>');
        });

        it('extracts content from HTML Component', function () {
            $html = '<html><body><x-card>Card content here</x-card></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->andReturn(false);

            $this->htmlComponentRegistry->shouldReceive('has')
                ->with('card')
                ->andReturn(true);

            $capturedContent = null;

            $this->htmlComponentRegistry->shouldReceive('render')
                ->with('card', \Mockery::capture($capturedContent), \Mockery::any())
                ->andReturn('<div class="card">Card content here</div>');

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $this->processor->process($dom, $context);

            // Assert content was captured
            expect($capturedContent)->toBe('Card content here');
        });
    });

    describe('Error Handling', function () {
        it('shows helpful error when component not found', function () {
            $html = '<html><body><x-unknown id="test" /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            // Neither registry has the component
            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('unknown')
                ->andReturn(false);

            $this->htmlComponentRegistry->shouldReceive('has')
                ->with('unknown')
                ->andReturn(false);

            // Mock available components for error message
            $this->liveComponentRegistry->shouldReceive('getAllComponentNames')
                ->andReturn(['counter', 'datatable']);

            $this->htmlComponentRegistry->shouldReceive('getAllComponentNames')
                ->andReturn(['button', 'card']);

            // Development mode
            $_ENV['APP_ENV'] = 'development';

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            // Should show error with available components
            $html = $result->document->saveHTML();
            expect($html)->toContain('Unknown component');
            expect($html)->toContain('counter, datatable');
            expect($html)->toContain('button, card');
        });

        it('removes component silently in production', function () {
            $html = '<html><body><p>Before</p><x-unknown /><p>After</p></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            $this->liveComponentRegistry->shouldReceive('isRegistered')->andReturn(false);
            $this->htmlComponentRegistry->shouldReceive('has')->andReturn(false);
            $this->liveComponentRegistry->shouldReceive('getAllComponentNames')->andReturn([]);
            $this->htmlComponentRegistry->shouldReceive('getAllComponentNames')->andReturn([]);

            // Production mode
            $_ENV['APP_ENV'] = 'production';

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            $html = $result->document->saveHTML();

            // Component should be removed, but surrounding content remains
            expect($html)->toContain('Before');
            expect($html)->toContain('After');
            expect($html)->not->toContain('x-unknown');
            expect($html)->not->toContain('error'); // No error message
        });
    });

    describe('Auto-Detection Priority', function () {
        it('prioritizes LiveComponent over HTML Component', function () {
            // Component registered in BOTH registries
            $html = '<html><body><x-button id="test" /></body></html>';
            $dom = $this->parser->parseToWrapper($html);

            // BOTH return true
            $this->liveComponentRegistry->shouldReceive('isRegistered')
                ->with('button')
                ->andReturn(true); // LiveComponent wins!

            $mockComponent = mock(LiveComponentContract::class);
            $mockComponent->shouldReceive('getId')
                ->andReturn(ComponentId::create('button', 'test'));
            $mockComponent->shouldReceive('getData')
                ->andReturn(ComponentData::fromArray([]));
            $mockComponent->shouldReceive('getRenderData')
                ->andReturn(new ComponentRenderData('test', []));

            $this->liveComponentRegistry->shouldReceive('getClassName')
                ->andReturn('TestButtonComponent');

            $this->liveComponentRegistry->shouldReceive('resolve')
                ->andReturn($mockComponent);

            $this->liveComponentRegistry->shouldReceive('renderWithWrapper')
                ->with($mockComponent)
                ->andReturn('<button data-component-id="button:test">LiveComponent Button</button>');

            $mockMetadata = new CompiledComponentMetadata(
                className: 'TestButtonComponent',
                componentName: 'button',
                properties: [],
                actions: [],
                constructorParams: []
            );
            $this->metadataCache->shouldReceive('get')->andReturn($mockMetadata);

            // HTML Component should NOT be called
            $this->htmlComponentRegistry->shouldNotReceive('render');

            // Process
            $context = new RenderContext(
                template: 'test-template',
                metaData: new MetaData('Test Component Processing'),
                data: []
            );
            $result = $this->processor->process($dom, $context);

            $html = $result->document->saveHTML();
            expect($html)->toContain('LiveComponent Button');
            expect($html)->toContain('data-component-id');
        });
    });
});
