"""Unit tests for the in-process event bus."""

import asyncio

import pytest

from strawpot_gui.event_bus import EventBus, SessionEvent


@pytest.fixture
def bus():
    return EventBus()


class TestEventBus:
    def test_publish_without_subscribers(self, bus):
        """Publishing with no subscribers should not raise."""
        bus.publish(SessionEvent(kind="session_started", run_id="run_1"))

    def test_subscribe_receives_events(self, bus):
        """Subscriber receives published events."""

        async def _run():
            received = []

            async def consumer():
                async for event in bus.subscribe():
                    received.append(event)
                    if len(received) >= 2:
                        break

            task = asyncio.create_task(consumer())
            await asyncio.sleep(0.01)

            bus.publish(SessionEvent(kind="session_started", run_id="run_1"))
            bus.publish(SessionEvent(kind="session_completed", run_id="run_1"))

            await asyncio.wait_for(task, timeout=2.0)

            assert len(received) == 2
            assert received[0].kind == "session_started"
            assert received[1].kind == "session_completed"

        asyncio.run(_run())

    def test_multiple_subscribers(self, bus):
        """Multiple subscribers each receive all events."""

        async def _run():
            received_1 = []
            received_2 = []

            async def consumer(target):
                async for event in bus.subscribe():
                    target.append(event)
                    if len(target) >= 1:
                        break

            t1 = asyncio.create_task(consumer(received_1))
            t2 = asyncio.create_task(consumer(received_2))

            await asyncio.sleep(0.01)

            bus.publish(SessionEvent(kind="session_started", run_id="run_1"))

            await asyncio.wait_for(asyncio.gather(t1, t2), timeout=2.0)

            assert len(received_1) == 1
            assert len(received_2) == 1

        asyncio.run(_run())

    def test_subscriber_cleanup_on_close(self, bus):
        """Subscriber is removed from bus when generator is closed."""

        async def _run():
            gen = bus.subscribe()
            # Start iterating to register the subscriber
            task = asyncio.ensure_future(gen.__anext__())
            await asyncio.sleep(0.01)
            assert len(bus._subscribers) == 1

            # Cancel the pending __anext__ first, then close the generator
            task.cancel()
            try:
                await task
            except (asyncio.CancelledError, StopAsyncIteration):
                pass
            await gen.aclose()
            assert len(bus._subscribers) == 0

        asyncio.run(_run())

    def test_full_queue_drops_event(self, bus):
        """Events are dropped when a subscriber's queue is full."""
        q = asyncio.Queue(maxsize=2)
        bus._subscribers.append(q)

        # Fill the queue
        bus.publish(SessionEvent(kind="session_started", run_id="run_1"))
        bus.publish(SessionEvent(kind="session_started", run_id="run_2"))
        # This should be silently dropped
        bus.publish(SessionEvent(kind="session_started", run_id="run_3"))

        assert q.qsize() == 2
