<?php

declare(strict_types=1);

use App\Framework\LiveComponents\Batch\BatchOperation;
use App\Framework\LiveComponents\Batch\BatchProcessor;
use App\Framework\LiveComponents\Batch\BatchRequest;
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\Contracts\LiveComponent;
use App\Framework\LiveComponents\LiveComponentHandler;
use App\Framework\LiveComponents\Rendering\FragmentRenderer;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentUpdate;
use App\Framework\View\Rendering\FragmentCollection;

describe('BatchProcessor', function () {
    beforeEach(function () {
        $this->componentRegistry = Mockery::mock(ComponentRegistry::class);
        $this->handler = Mockery::mock(LiveComponentHandler::class);
        $this->fragmentRenderer = Mockery::mock(FragmentRenderer::class);

        $this->processor = new BatchProcessor(
            $this->componentRegistry,
            $this->handler,
            $this->fragmentRenderer
        );
    });

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

    it('processes single operation successfully', function () {
        $operation = new BatchOperation('counter:demo', 'increment', ['amount' => 5]);
        $request = new BatchRequest($operation);

        $component = Mockery::mock(LiveComponent::class);
        $component->shouldReceive('getId')->andReturn(ComponentId::fromString('counter:demo'));
        $component->shouldReceive('getData')->andReturn(ComponentData::fromArray(['count' => 5]));

        $this->componentRegistry
            ->shouldReceive('resolve')
            ->once()
            ->andReturn($component);

        $update = new ComponentUpdate(
            component: $component,
            state: ComponentData::fromArray(['count' => 10]),
            events: [['type' => 'incremented']]
        );

        $this->handler
            ->shouldReceive('handleAction')
            ->once()
            ->andReturn($update);

        $this->componentRegistry
            ->shouldReceive('render')
            ->once()
            ->with($component)
            ->andReturn('<div>Counter: 10</div>');

        $response = $this->processor->process($request);

        expect($response->totalOperations)->toBe(1);
        expect($response->successCount)->toBe(1);
        expect($response->failureCount)->toBe(0);
        expect($response->results[0]->success)->toBeTrue();
        expect($response->results[0]->html)->toBe('<div>Counter: 10</div>');
        expect($response->results[0]->state)->toBe(['count' => 10]);
        expect($response->results[0]->events)->toBe([['type' => 'incremented']]);
    });

    it('processes multiple operations successfully', function () {
        $op1 = new BatchOperation('counter:demo', 'increment');
        $op2 = new BatchOperation('stats:user', 'refresh');
        $request = new BatchRequest($op1, $op2);

        $component1 = Mockery::mock(LiveComponent::class);
        $component1->shouldReceive('getId')->andReturn(ComponentId::fromString('counter:demo'));
        $component1->shouldReceive('getData')->andReturn(ComponentData::fromArray(['count' => 1]));

        $component2 = Mockery::mock(LiveComponent::class);
        $component2->shouldReceive('getId')->andReturn(ComponentId::fromString('stats:user'));
        $component2->shouldReceive('getData')->andReturn(ComponentData::fromArray(['views' => 100]));

        $this->componentRegistry
            ->shouldReceive('resolve')
            ->twice()
            ->andReturn($component1, $component2);

        $update1 = new ComponentUpdate(
            component: $component1,
            state: ComponentData::fromArray(['count' => 2]),
            events: []
        );

        $update2 = new ComponentUpdate(
            component: $component2,
            state: ComponentData::fromArray(['views' => 101]),
            events: []
        );

        $this->handler
            ->shouldReceive('handleAction')
            ->twice()
            ->andReturn($update1, $update2);

        $this->componentRegistry
            ->shouldReceive('render')
            ->twice()
            ->andReturn('<div>Counter: 2</div>', '<div>Views: 101</div>');

        $response = $this->processor->process($request);

        expect($response->totalOperations)->toBe(2);
        expect($response->successCount)->toBe(2);
        expect($response->failureCount)->toBe(0);
    });

    it('handles operation failure with error isolation', function () {
        $op1 = new BatchOperation('counter:demo', 'increment');
        $op2 = new BatchOperation('invalid:component', 'action');
        $op3 = new BatchOperation('stats:user', 'refresh');
        $request = new BatchRequest($op1, $op2, $op3);

        $component1 = Mockery::mock(LiveComponent::class);
        $component1->shouldReceive('getId')->andReturn(ComponentId::fromString('counter:demo'));
        $component1->shouldReceive('getData')->andReturn(ComponentData::fromArray(['count' => 1]));

        $component3 = Mockery::mock(LiveComponent::class);
        $component3->shouldReceive('getId')->andReturn(ComponentId::fromString('stats:user'));
        $component3->shouldReceive('getData')->andReturn(ComponentData::fromArray(['views' => 100]));

        $this->componentRegistry
            ->shouldReceive('resolve')
            ->times(3)
            ->andReturnUsing(function ($id) use ($component1, $component3) {
                if ($id->toString() === 'invalid:component') {
                    throw new InvalidArgumentException('Unknown component: invalid:component');
                }

                return $id->toString() === 'counter:demo' ? $component1 : $component3;
            });

        $update1 = new ComponentUpdate(
            component: $component1,
            state: ComponentData::fromArray(['count' => 2]),
            events: []
        );

        $update3 = new ComponentUpdate(
            component: $component3,
            state: ComponentData::fromArray(['views' => 101]),
            events: []
        );

        $this->handler
            ->shouldReceive('handleAction')
            ->twice()
            ->andReturn($update1, $update3);

        $this->componentRegistry
            ->shouldReceive('render')
            ->twice()
            ->andReturn('<div>Counter: 2</div>', '<div>Views: 101</div>');

        $response = $this->processor->process($request);

        expect($response->totalOperations)->toBe(3);
        expect($response->successCount)->toBe(2);
        expect($response->failureCount)->toBe(1);

        // First operation succeeds
        expect($response->results[0]->success)->toBeTrue();

        // Second operation fails
        expect($response->results[1]->success)->toBeFalse();
        expect($response->results[1]->error)->toContain('Unknown component');
        expect($response->results[1]->errorCode)->toBe('COMPONENT_NOT_FOUND');

        // Third operation succeeds (error isolation works)
        expect($response->results[2]->success)->toBeTrue();
    });

    it('processes fragment-based operation', function () {
        $operation = new BatchOperation(
            componentId: 'counter:demo',
            method: 'increment',
            fragments: ['counter-display']
        );
        $request = new BatchRequest($operation);

        $component = Mockery::mock(LiveComponent::class);
        $component->shouldReceive('getId')->andReturn(ComponentId::fromString('counter:demo'));
        $component->shouldReceive('getData')->andReturn(ComponentData::fromArray(['count' => 5]));

        $this->componentRegistry
            ->shouldReceive('resolve')
            ->once()
            ->andReturn($component);

        $update = new ComponentUpdate(
            component: $component,
            state: ComponentData::fromArray(['count' => 10]),
            events: []
        );

        $this->handler
            ->shouldReceive('handleAction')
            ->once()
            ->andReturn($update);

        $fragmentCollection = FragmentCollection::fromArray([
            'counter-display' => '<span>10</span>',
        ]);

        $this->fragmentRenderer
            ->shouldReceive('renderFragments')
            ->once()
            ->with($component, ['counter-display'])
            ->andReturn($fragmentCollection);

        $response = $this->processor->process($request);

        expect($response->successCount)->toBe(1);
        expect($response->results[0]->fragments)->toBe(['counter-display' => '<span>10</span>']);
        expect($response->results[0]->html)->toBeNull();
    });

    it('preserves operation ids in results', function () {
        $op1 = new BatchOperation('counter:demo', 'increment', operationId: 'op-1');
        $op2 = new BatchOperation('stats:user', 'refresh', operationId: 'op-2');
        $request = new BatchRequest($op1, $op2);

        $component1 = Mockery::mock(LiveComponent::class);
        $component1->shouldReceive('getId')->andReturn(ComponentId::fromString('counter:demo'));
        $component1->shouldReceive('getData')->andReturn(ComponentData::fromArray([]));

        $component2 = Mockery::mock(LiveComponent::class);
        $component2->shouldReceive('getId')->andReturn(ComponentId::fromString('stats:user'));
        $component2->shouldReceive('getData')->andReturn(ComponentData::fromArray([]));

        $this->componentRegistry
            ->shouldReceive('resolve')
            ->twice()
            ->andReturn($component1, $component2);

        $this->handler
            ->shouldReceive('handleAction')
            ->twice()
            ->andReturn(
                new ComponentUpdate($component1, ComponentData::fromArray([]), []),
                new ComponentUpdate($component2, ComponentData::fromArray([]), [])
            );

        $this->componentRegistry
            ->shouldReceive('render')
            ->twice()
            ->andReturn('<div>1</div>', '<div>2</div>');

        $response = $this->processor->process($request);

        expect($response->results[0]->operationId)->toBe('op-1');
        expect($response->results[1]->operationId)->toBe('op-2');
    });
});
