"""
Tests for engine.eventbus module.
"""


from engine.eventbus import EventBus, get_event_bus, set_event_bus
from engine.events import EventType, NtfyMessageEvent


class TestEventBusInit:
    """Tests for EventBus initialization."""

    def test_init_creates_empty_subscribers(self):
        """EventBus starts with no subscribers."""
        bus = EventBus()
        assert bus.subscriber_count() == 0


class TestEventBusSubscribe:
    """Tests for EventBus.subscribe method."""

    def test_subscribe_adds_callback(self):
        """subscribe() adds a callback for an event type."""
        bus = EventBus()
        def callback(e):
            return None

        bus.subscribe(EventType.NTFY_MESSAGE, callback)

        assert bus.subscriber_count(EventType.NTFY_MESSAGE) == 1

    def test_subscribe_multiple_callbacks_same_event(self):
        """Multiple callbacks can be subscribed to the same event type."""
        bus = EventBus()
        def cb1(e):
            return None
        def cb2(e):
            return None

        bus.subscribe(EventType.NTFY_MESSAGE, cb1)
        bus.subscribe(EventType.NTFY_MESSAGE, cb2)

        assert bus.subscriber_count(EventType.NTFY_MESSAGE) == 2

    def test_subscribe_different_event_types(self):
        """Callbacks can be subscribed to different event types."""
        bus = EventBus()
        def cb1(e):
            return None
        def cb2(e):
            return None

        bus.subscribe(EventType.NTFY_MESSAGE, cb1)
        bus.subscribe(EventType.MIC_LEVEL, cb2)

        assert bus.subscriber_count(EventType.NTFY_MESSAGE) == 1
        assert bus.subscriber_count(EventType.MIC_LEVEL) == 1


class TestEventBusUnsubscribe:
    """Tests for EventBus.unsubscribe method."""

    def test_unsubscribe_removes_callback(self):
        """unsubscribe() removes a callback."""
        bus = EventBus()
        def callback(e):
            return None

        bus.subscribe(EventType.NTFY_MESSAGE, callback)
        bus.unsubscribe(EventType.NTFY_MESSAGE, callback)

        assert bus.subscriber_count(EventType.NTFY_MESSAGE) == 0

    def test_unsubscribe_nonexistent_callback_no_error(self):
        """unsubscribe() handles non-existent callback gracefully."""
        bus = EventBus()
        def callback(e):
            return None

        bus.unsubscribe(EventType.NTFY_MESSAGE, callback)


class TestEventBusPublish:
    """Tests for EventBus.publish method."""

    def test_publish_calls_subscriber(self):
        """publish() calls the subscriber callback."""
        bus = EventBus()
        received = []

        def callback(event):
            received.append(event)

        bus.subscribe(EventType.NTFY_MESSAGE, callback)
        event = NtfyMessageEvent(title="Test", body="Body")
        bus.publish(EventType.NTFY_MESSAGE, event)

        assert len(received) == 1
        assert received[0].title == "Test"

    def test_publish_multiple_subscribers(self):
        """publish() calls all subscribers for an event type."""
        bus = EventBus()
        received1 = []
        received2 = []

        def callback1(event):
            received1.append(event)

        def callback2(event):
            received2.append(event)

        bus.subscribe(EventType.NTFY_MESSAGE, callback1)
        bus.subscribe(EventType.NTFY_MESSAGE, callback2)
        event = NtfyMessageEvent(title="Test", body="Body")
        bus.publish(EventType.NTFY_MESSAGE, event)

        assert len(received1) == 1
        assert len(received2) == 1

    def test_publish_different_event_types(self):
        """publish() only calls subscribers for the specific event type."""
        bus = EventBus()
        ntfy_received = []
        mic_received = []

        def ntfy_callback(event):
            ntfy_received.append(event)

        def mic_callback(event):
            mic_received.append(event)

        bus.subscribe(EventType.NTFY_MESSAGE, ntfy_callback)
        bus.subscribe(EventType.MIC_LEVEL, mic_callback)
        event = NtfyMessageEvent(title="Test", body="Body")
        bus.publish(EventType.NTFY_MESSAGE, event)

        assert len(ntfy_received) == 1
        assert len(mic_received) == 0


class TestEventBusClear:
    """Tests for EventBus.clear method."""

    def test_clear_removes_all_subscribers(self):
        """clear() removes all subscribers."""
        bus = EventBus()
        def cb1(e):
            return None
        def cb2(e):
            return None

        bus.subscribe(EventType.NTFY_MESSAGE, cb1)
        bus.subscribe(EventType.MIC_LEVEL, cb2)
        bus.clear()

        assert bus.subscriber_count() == 0


class TestEventBusThreadSafety:
    """Tests for EventBus thread safety."""

    def test_concurrent_subscribe_unsubscribe(self):
        """subscribe and unsubscribe can be called concurrently."""
        import threading

        bus = EventBus()
        callbacks = [lambda e: None for _ in range(10)]

        def subscribe():
            for cb in callbacks:
                bus.subscribe(EventType.NTFY_MESSAGE, cb)

        def unsubscribe():
            for cb in callbacks:
                bus.unsubscribe(EventType.NTFY_MESSAGE, cb)

        t1 = threading.Thread(target=subscribe)
        t2 = threading.Thread(target=unsubscribe)
        t1.start()
        t2.start()
        t1.join()
        t2.join()


class TestGlobalEventBus:
    """Tests for global event bus functions."""

    def test_get_event_bus_returns_singleton(self):
        """get_event_bus() returns the same instance."""
        bus1 = get_event_bus()
        bus2 = get_event_bus()
        assert bus1 is bus2

    def test_set_event_bus_replaces_singleton(self):
        """set_event_bus() replaces the global event bus."""
        new_bus = EventBus()
        set_event_bus(new_bus)
        try:
            assert get_event_bus() is new_bus
        finally:
            set_event_bus(None)
