"""Tests for core.bus — EventBus pub/sub system."""

import time
import threading

import pytest

from core.bus import EventBus, Event


class TestEventBus:

    def test_subscribe_and_publish(self, mock_bus):
        """A subscriber receives an event that was published."""
        received = []

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

        mock_bus.subscribe(handler, event_type="HOST_DISCOVERED")
        mock_bus.publish(Event(
            event_type="HOST_DISCOVERED",
            payload={"ip": "10.0.0.1"},
            source_module="test",
        ))

        # Wait for dispatch
        deadline = time.time() + 3.0
        while not received and time.time() < deadline:
            time.sleep(0.05)

        assert len(received) == 1
        assert received[0].event_type == "HOST_DISCOVERED"
        assert received[0].payload["ip"] == "10.0.0.1"

    def test_publish_no_subscribers(self, mock_bus):
        """Publishing with no subscribers does not raise an error."""
        mock_bus.publish(Event(
            event_type="CREDENTIAL_FOUND",
            payload={"user": "admin"},
            source_module="test",
        ))
        # Give the dispatcher a moment to process
        time.sleep(0.2)
        # No crash = pass

    def test_multiple_subscribers(self, mock_bus):
        """Multiple subscribers for the same event type all receive the event."""
        results_a = []
        results_b = []

        mock_bus.subscribe(lambda e: results_a.append(e), event_type="PCAP_ROTATED")
        mock_bus.subscribe(lambda e: results_b.append(e), event_type="PCAP_ROTATED")

        mock_bus.publish(Event(
            event_type="PCAP_ROTATED",
            payload={"file": "/tmp/test.pcap"},
            source_module="test",
        ))

        deadline = time.time() + 3.0
        while (not results_a or not results_b) and time.time() < deadline:
            time.sleep(0.05)

        assert len(results_a) == 1
        assert len(results_b) == 1

    def test_event_type_filtering(self, mock_bus):
        """A subscriber only receives events matching its registered type."""
        received = []

        mock_bus.subscribe(lambda e: received.append(e), event_type="CREDENTIAL_FOUND")

        # Publish a different event type
        mock_bus.publish(Event(
            event_type="HOST_DISCOVERED",
            payload={"ip": "10.0.0.2"},
            source_module="test",
        ))

        # And the matching type
        mock_bus.publish(Event(
            event_type="CREDENTIAL_FOUND",
            payload={"user": "root"},
            source_module="test",
        ))

        deadline = time.time() + 3.0
        while len(received) < 1 and time.time() < deadline:
            time.sleep(0.05)

        # Allow a little extra time for any misdelivered events
        time.sleep(0.2)

        assert len(received) == 1
        assert received[0].event_type == "CREDENTIAL_FOUND"

    def test_wildcard_subscriber(self, mock_bus):
        """A wildcard subscriber (event_type=None) receives all events."""
        received = []

        mock_bus.subscribe(lambda e: received.append(e), event_type=None)

        mock_bus.publish(Event(event_type="HOST_DISCOVERED", payload={}, source_module="test"))
        mock_bus.publish(Event(event_type="CREDENTIAL_FOUND", payload={}, source_module="test"))

        deadline = time.time() + 3.0
        while len(received) < 2 and time.time() < deadline:
            time.sleep(0.05)

        assert len(received) == 2
        types = {e.event_type for e in received}
        assert "HOST_DISCOVERED" in types
        assert "CREDENTIAL_FOUND" in types

    def test_event_dataclass(self):
        """Event dataclass fields and to_dict() work correctly."""
        before = time.time()
        event = Event(
            event_type="MODULE_STARTED",
            payload={"module": "dns_logger", "pid": 1234},
            source_module="engine",
        )
        after = time.time()

        assert event.event_type == "MODULE_STARTED"
        assert event.payload["module"] == "dns_logger"
        assert event.source_module == "engine"
        assert before <= event.timestamp <= after

        d = event.to_dict()
        assert isinstance(d, dict)
        assert d["event_type"] == "MODULE_STARTED"
        assert d["payload"]["pid"] == 1234
