"""Tests for enterprise event bus."""

from __future__ import annotations

import threading

from orxaq.enterprise.events import (
    Event,
    EventBus,
    EventPriority,
)

# -- Event --------------------------------------------------------------------


class TestEvent:
    def test_creation(self):
        evt = Event(topic="build.start", data={"step": 1})
        assert evt.topic == "build.start"
        assert evt.data == {"step": 1}
        assert evt.priority == EventPriority.NORMAL
        assert evt.timestamp > 0
        assert evt.source == ""

    def test_to_dict_format(self):
        evt = Event(
            topic="deploy",
            data={"env": "prod"},
            priority=EventPriority.CRITICAL,
            timestamp=1234.0,
            source="ci",
        )
        d = evt.to_dict()
        assert d == {
            "topic": "deploy",
            "data": {"env": "prod"},
            "priority": 3,
            "timestamp": 1234.0,
            "source": "ci",
        }

    def test_frozen(self):
        evt = Event(topic="x", data={})
        try:
            evt.topic = "y"  # type: ignore[misc]
            raise AssertionError("should be frozen")
        except AttributeError:
            pass


# -- EventPriority ------------------------------------------------------------


class TestEventPriority:
    def test_ordering(self):
        assert EventPriority.LOW.value < EventPriority.NORMAL.value
        assert EventPriority.NORMAL.value < EventPriority.HIGH.value
        assert EventPriority.HIGH.value < EventPriority.CRITICAL.value

    def test_values(self):
        assert EventPriority.LOW.value == 0
        assert EventPriority.NORMAL.value == 1
        assert EventPriority.HIGH.value == 2
        assert EventPriority.CRITICAL.value == 3


# -- EventBus -----------------------------------------------------------------


class TestEventBus:
    def test_creation(self):
        bus = EventBus()
        assert bus.history() == []
        assert bus.topics() == []

    def test_subscribe_and_publish(self):
        bus = EventBus()
        received = []
        bus.subscribe("test", lambda e: received.append(e))
        bus.publish("test", data={"v": 1})
        assert len(received) == 1
        assert received[0].topic == "test"
        assert received[0].data == {"v": 1}

    def test_unsubscribe_stops_delivery(self):
        bus = EventBus()
        received = []

        def cb(e):
            received.append(e)

        bus.subscribe("test", cb)
        bus.publish("test")
        assert len(received) == 1

        bus.unsubscribe("test", cb)
        bus.publish("test")
        assert len(received) == 1  # no new event

    def test_wildcard_subscriber(self):
        bus = EventBus()
        all_events = []
        bus.subscribe("*", lambda e: all_events.append(e))
        bus.publish("topic.a", data={"a": 1})
        bus.publish("topic.b", data={"b": 2})
        assert len(all_events) == 2
        assert all_events[0].topic == "topic.a"
        assert all_events[1].topic == "topic.b"

    def test_publish_returns_event(self):
        bus = EventBus()
        evt = bus.publish("hello", data={"x": 1}, source="unit-test")
        assert isinstance(evt, Event)
        assert evt.topic == "hello"
        assert evt.source == "unit-test"

    def test_publish_with_priority(self):
        bus = EventBus()
        evt = bus.publish("alert", priority=EventPriority.CRITICAL)
        assert evt.priority == EventPriority.CRITICAL

    def test_history_recording(self):
        bus = EventBus()
        bus.publish("a")
        bus.publish("b")
        bus.publish("c")
        h = bus.history()
        assert len(h) == 3
        assert h[0].topic == "a"
        assert h[2].topic == "c"

    def test_history_limit(self):
        bus = EventBus(max_history=5)
        for i in range(10):
            bus.publish(f"t{i}")
        h = bus.history()
        assert len(h) == 5
        # Oldest trimmed, newest kept
        assert h[0].topic == "t5"
        assert h[-1].topic == "t9"

    def test_history_filtered_by_topic(self):
        bus = EventBus()
        bus.publish("alpha")
        bus.publish("beta")
        bus.publish("alpha")
        h = bus.history(topic="alpha")
        assert len(h) == 2
        assert all(e.topic == "alpha" for e in h)

    def test_history_limit_parameter(self):
        bus = EventBus()
        for i in range(10):
            bus.publish("x")
        h = bus.history(limit=3)
        assert len(h) == 3

    def test_topics_returns_subscribed(self):
        bus = EventBus()
        bus.subscribe("zebra", lambda _e: None)
        bus.subscribe("alpha", lambda _e: None)
        assert bus.topics() == ["alpha", "zebra"]

    def test_clear_history(self):
        bus = EventBus()
        bus.publish("a")
        bus.publish("b")
        assert len(bus.history()) == 2
        bus.clear_history()
        assert len(bus.history()) == 0

    def test_callback_exception_does_not_break_bus(self):
        bus = EventBus()
        received = []

        def bad_cb(_e):
            msg = "boom"
            raise RuntimeError(msg)

        bus.subscribe("t", bad_cb)
        bus.subscribe("t", lambda e: received.append(e))
        bus.publish("t", data={"ok": True})
        # Second subscriber still receives the event
        assert len(received) == 1
        assert received[0].data == {"ok": True}

    def test_thread_safety(self):
        bus = EventBus()
        received = []
        lock = threading.Lock()

        def cb(e):
            with lock:
                received.append(e)

        bus.subscribe("mt", cb)
        threads = []
        for i in range(20):
            t = threading.Thread(target=bus.publish, args=("mt",), kwargs={"data": {"i": i}})
            threads.append(t)

        for t in threads:
            t.start()
        for t in threads:
            t.join()

        assert len(received) == 20
        assert len(bus.history(topic="mt")) == 20

    def test_publish_default_data(self):
        bus = EventBus()
        evt = bus.publish("empty")
        assert evt.data == {}
