<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Sse;

use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\Router\Result\SseEvent;
use App\Framework\Sse\SseBroadcaster;
use App\Framework\Sse\SseConnectionPool;
use App\Framework\Sse\ValueObjects\ConnectionId;
use App\Framework\Sse\ValueObjects\SseChannel;
use App\Framework\Sse\ValueObjects\SseConnection;
use Tests\Support\MockSseStream;

describe('SseBroadcaster', function () {
    beforeEach(function () {
        $this->pool = new SseConnectionPool();
        $this->broadcaster = new SseBroadcaster($this->pool);
    });

    describe('Basic Broadcasting', function () {
        it('broadcasts raw SSE event to channel', function () {
            $channel = SseChannel::forUser('user123');
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel],
                userId: 'user123'
            );

            $this->pool->add($connection);

            $event = new SseEvent(
                data: json_encode(['message' => 'test']),
                event: 'custom-event'
            );

            $sent = $this->broadcaster->broadcast($event, $channel);

            expect($sent)->toBe(1);
            expect($stream->getSentEventsCount())->toBe(1);
        });

        it('broadcasts to multiple channels at once', function () {
            $userChannel = SseChannel::forUser('user123');
            $systemChannel = SseChannel::system();

            $stream1 = new MockSseStream();
            $connection1 = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream1,
                channels: [$userChannel]
            );

            $stream2 = new MockSseStream();
            $connection2 = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream2,
                channels: [$systemChannel]
            );

            $this->pool->add($connection1);
            $this->pool->add($connection2);

            $event = new SseEvent(data: 'test', event: 'test');
            $sent = $this->broadcaster->broadcastToMultipleChannels(
                $event,
                [$userChannel, $systemChannel]
            );

            expect($sent)->toBe(2);
        });

        it('broadcasts JSON data', function () {
            $channel = SseChannel::system();
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastJson(
                ['foo' => 'bar', 'value' => 123],
                $channel,
                'data-event'
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('data-event');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events); // Re-index array
            $data = json_decode($eventArray[0]->data, true);
            expect($data['foo'])->toBe('bar');
            expect($data['value'])->toBe(123);
        });
    });

    describe('Event Batching', function () {
        it('enables event batching with configuration', function () {
            $this->broadcaster->enableBatching(maxBatchSize: 5, maxBatchDelayMs: 50);

            $channel = SseChannel::system();
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            // Send 3 events (below batch size)
            for ($i = 0; $i < 3; $i++) {
                $event = new SseEvent(data: "event-{$i}", event: 'test');
                $this->broadcaster->broadcast($event, $channel);
            }

            // Events should be batched, not sent yet
            expect($stream->getSentEventsCount())->toBe(0);

            // Flush manually
            $this->broadcaster->flushAll();

            // Now should have 1 batch event
            expect($stream->getSentEventsCount())->toBe(1);

            $batchEvents = $stream->getSentEventsByType('batch');
            expect($batchEvents)->toHaveCount(1);

            $eventArray = array_values($batchEvents);
            $batchData = json_decode($eventArray[0]->data, true);
            expect($batchData['type'])->toBe('batch');
            expect($batchData['count'])->toBe(3);
            expect($batchData['events'])->toHaveCount(3);
        });

        it('auto-flushes when batch size reached', function () {
            $this->broadcaster->enableBatching(maxBatchSize: 3, maxBatchDelayMs: 1000);

            $channel = SseChannel::system();
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            // Send exactly batch size
            for ($i = 0; $i < 3; $i++) {
                $event = new SseEvent(data: "event-{$i}", event: 'test');
                $sent = $this->broadcaster->broadcast($event, $channel);

                if ($i < 2) {
                    expect($sent)->toBe(0); // Not flushed yet
                } else {
                    expect($sent)->toBe(1); // Auto-flushed on 3rd event
                }
            }

            expect($stream->getSentEventsCount())->toBe(1);
        });

        it('disables batching and flushes pending events', function () {
            $this->broadcaster->enableBatching(maxBatchSize: 10, maxBatchDelayMs: 100);

            $channel = SseChannel::system();
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            // Add some events to batch
            for ($i = 0; $i < 3; $i++) {
                $event = new SseEvent(data: "event-{$i}", event: 'test');
                $this->broadcaster->broadcast($event, $channel);
            }

            expect($stream->getSentEventsCount())->toBe(0);

            // Disable batching - should flush
            $this->broadcaster->disableBatching();

            expect($stream->getSentEventsCount())->toBe(1);

            // New events should be sent immediately
            $event = new SseEvent(data: 'immediate', event: 'test');
            $sent = $this->broadcaster->broadcast($event, $channel);

            expect($sent)->toBe(1);
            expect($stream->getSentEventsCount())->toBe(2);
        });
    });

    describe('Component Updates', function () {
        it('broadcasts component update', function () {
            $componentId = ComponentId::fromString('todo-list:instance-123');
            $channel = SseChannel::forComponent($componentId);
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastComponentUpdate(
                $componentId,
                state: ['count' => 5],
                html: '<div>Updated HTML</div>',
                events: ['item-added']
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('component-update');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events);
            $data = json_decode($eventArray[0]->data, true);
            expect($data['componentId'])->toBe('todo-list:instance-123');
            expect($data['state']['count'])->toBe(5);
            expect($data['html'])->toBe('<div>Updated HTML</div>');
            expect($data['events'])->toBe(['item-added']);
        });

        it('broadcasts component fragments', function () {
            $componentId = ComponentId::fromString('todo-list:instance-123');
            $channel = SseChannel::forComponent($componentId);
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastComponentFragments(
                $componentId,
                fragments: [
                    'header' => '<h1>Updated</h1>',
                    'footer' => '<p>New footer</p>'
                ],
                state: ['count' => 10],
                events: []
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('component-fragments');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events);
            $data = json_decode($eventArray[0]->data, true);
            expect($data['componentId'])->toBe('todo-list:instance-123');
            expect($data['fragments']['header'])->toBe('<h1>Updated</h1>');
            expect($data['fragments']['footer'])->toBe('<p>New footer</p>');
        });
    });

    describe('Presence Broadcasting', function () {
        it('broadcasts presence update', function () {
            $channel = SseChannel::presence('room-123');
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel]
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastPresence(
                room: 'room-123',
                userId: 'user456',
                status: 'online',
                metadata: ['name' => 'John Doe']
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('presence');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events);
            $data = json_decode($eventArray[0]->data, true);
            expect($data['userId'])->toBe('user456');
            expect($data['status'])->toBe('online');
            expect($data['metadata']['name'])->toBe('John Doe');
            expect($data['timestamp'])->toBeInt();
        });
    });

    describe('Notification Broadcasting', function () {
        it('broadcasts notification to user', function () {
            $channel = SseChannel::forUser('user123');
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel],
                userId: 'user123'
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastNotification(
                userId: 'user123',
                title: 'New Message',
                message: 'You have a new message',
                type: 'info',
                data: ['messageId' => 789]
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('notification');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events);
            $data = json_decode($eventArray[0]->data, true);
            expect($data['title'])->toBe('New Message');
            expect($data['message'])->toBe('You have a new message');
            expect($data['type'])->toBe('info');
            expect($data['data']['messageId'])->toBe(789);
        });
    });

    describe('Progress Broadcasting', function () {
        it('broadcasts progress update', function () {
            $channel = SseChannel::forUser('user123');
            $stream = new MockSseStream();

            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [$channel],
                userId: 'user123'
            );

            $this->pool->add($connection);

            $sent = $this->broadcaster->broadcastProgress(
                userId: 'user123',
                taskId: 'export-123',
                percent: 75,
                message: 'Exporting data...',
                data: ['rows_processed' => 7500]
            );

            expect($sent)->toBe(1);

            $events = $stream->getSentEventsByType('progress');
            expect($events)->toHaveCount(1);

            $eventArray = array_values($events);
            $data = json_decode($eventArray[0]->data, true);
            expect($data['taskId'])->toBe('export-123');
            expect($data['percent'])->toBe(75);
            expect($data['message'])->toBe('Exporting data...');
            expect($data['data']['rows_processed'])->toBe(7500);
        });
    });

    describe('System Announcements', function () {
        it('broadcasts system announcement to all clients', function () {
            $channel = SseChannel::system();

            // Create multiple connections
            $streams = [];
            for ($i = 0; $i < 3; $i++) {
                $stream = new MockSseStream();
                $streams[] = $stream;

                $connection = new SseConnection(
                    connectionId: ConnectionId::generate(),
                    stream: $stream,
                    channels: [$channel]
                );

                $this->pool->add($connection);
            }

            $sent = $this->broadcaster->broadcastSystemAnnouncement(
                message: 'System maintenance in 10 minutes',
                level: 'warning',
                data: ['scheduled_time' => '2024-01-15T10:00:00Z']
            );

            expect($sent)->toBe(3);

            foreach ($streams as $stream) {
                $events = $stream->getSentEventsByType('system-announcement');
                expect($events)->toHaveCount(1);

                $eventArray = array_values($events);
                $data = json_decode($eventArray[0]->data, true);
                expect($data['message'])->toBe('System maintenance in 10 minutes');
                expect($data['level'])->toBe('warning');
            }
        });
    });

    describe('Statistics', function () {
        it('retrieves connection pool stats', function () {
            $stream = new MockSseStream();
            $connection = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream,
                channels: [SseChannel::system()],
                userId: 'user123'
            );

            $this->pool->add($connection);

            $stats = $this->broadcaster->getStats();

            expect($stats['total_connections'])->toBe(1);
            expect($stats['authenticated_connections'])->toBe(1);
            expect($stats['anonymous_connections'])->toBe(0);
        });
    });

    describe('Edge Cases', function () {
        it('handles broadcasting to channel with no connections', function () {
            $channel = SseChannel::forUser('nonexistent');
            $event = new SseEvent(data: 'test', event: 'test');

            $sent = $this->broadcaster->broadcast($event, $channel);

            expect($sent)->toBe(0);
        });

        it('broadcasts to multiple users with different channels', function () {
            $user1Channel = SseChannel::forUser('user1');
            $user2Channel = SseChannel::forUser('user2');

            $stream1 = new MockSseStream();
            $connection1 = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream1,
                channels: [$user1Channel],
                userId: 'user1'
            );

            $stream2 = new MockSseStream();
            $connection2 = new SseConnection(
                connectionId: ConnectionId::generate(),
                stream: $stream2,
                channels: [$user2Channel],
                userId: 'user2'
            );

            $this->pool->add($connection1);
            $this->pool->add($connection2);

            // Broadcast to user1
            $sent1 = $this->broadcaster->broadcastNotification(
                'user1',
                'User 1 Notification',
                'Message for user 1'
            );

            expect($sent1)->toBe(1);
            expect($stream1->getSentEventsCount())->toBe(1);
            expect($stream2->getSentEventsCount())->toBe(0);

            // Broadcast to user2
            $sent2 = $this->broadcaster->broadcastNotification(
                'user2',
                'User 2 Notification',
                'Message for user 2'
            );

            expect($sent2)->toBe(1);
            expect($stream1->getSentEventsCount())->toBe(1); // Still 1
            expect($stream2->getSentEventsCount())->toBe(1); // Now 1
        });
    });
});
