"""Tests for the event bus pub/sub system."""
import pytest

from ayn_antivirus.core.event_bus import EventBus, EventType


def test_subscribe_and_publish():
    bus = EventBus()
    received = []
    bus.subscribe(EventType.THREAT_FOUND, lambda et, data: received.append(data))
    bus.publish(EventType.THREAT_FOUND, {"test": True})
    assert len(received) == 1
    assert received[0]["test"] is True


def test_multiple_subscribers():
    bus = EventBus()
    r1, r2 = [], []
    bus.subscribe(EventType.SCAN_STARTED, lambda et, d: r1.append(d))
    bus.subscribe(EventType.SCAN_STARTED, lambda et, d: r2.append(d))
    bus.publish(EventType.SCAN_STARTED, "go")
    assert len(r1) == 1
    assert len(r2) == 1


def test_unsubscribe():
    bus = EventBus()
    received = []
    cb = lambda et, d: received.append(d)
    bus.subscribe(EventType.FILE_SCANNED, cb)
    bus.unsubscribe(EventType.FILE_SCANNED, cb)
    bus.publish(EventType.FILE_SCANNED, "data")
    assert len(received) == 0


def test_unsubscribe_nonexistent():
    """Unsubscribing a callback that was never registered should not crash."""
    bus = EventBus()
    bus.unsubscribe(EventType.FILE_SCANNED, lambda et, d: None)


def test_publish_no_subscribers():
    """Publishing with no subscribers should not crash."""
    bus = EventBus()
    bus.publish(EventType.SCAN_COMPLETED, "no crash")


def test_subscriber_exception_isolated():
    """A failing subscriber must not prevent other subscribers from running."""
    bus = EventBus()
    received = []
    bus.subscribe(EventType.THREAT_FOUND, lambda et, d: 1 / 0)  # will raise
    bus.subscribe(EventType.THREAT_FOUND, lambda et, d: received.append(d))
    bus.publish(EventType.THREAT_FOUND, "data")
    assert len(received) == 1


def test_all_event_types():
    """Every EventType value can be published without error."""
    bus = EventBus()
    for et in EventType:
        bus.publish(et, None)


def test_clear_all():
    bus = EventBus()
    received = []
    bus.subscribe(EventType.THREAT_FOUND, lambda et, d: received.append(d))
    bus.subscribe(EventType.SCAN_STARTED, lambda et, d: received.append(d))
    bus.clear()
    bus.publish(EventType.THREAT_FOUND, "a")
    bus.publish(EventType.SCAN_STARTED, "b")
    assert len(received) == 0


def test_clear_single_event():
    bus = EventBus()
    r1, r2 = [], []
    bus.subscribe(EventType.THREAT_FOUND, lambda et, d: r1.append(d))
    bus.subscribe(EventType.SCAN_STARTED, lambda et, d: r2.append(d))
    bus.clear(EventType.THREAT_FOUND)
    bus.publish(EventType.THREAT_FOUND, "a")
    bus.publish(EventType.SCAN_STARTED, "b")
    assert len(r1) == 0  # cleared
    assert len(r2) == 1  # still active


def test_callback_receives_event_type():
    """Callback receives (event_type, data) — verify event_type is correct."""
    bus = EventBus()
    calls = []
    bus.subscribe(EventType.QUARANTINE_ACTION, lambda et, d: calls.append((et, d)))
    bus.publish(EventType.QUARANTINE_ACTION, "payload")
    assert calls[0][0] is EventType.QUARANTINE_ACTION
    assert calls[0][1] == "payload"


def test_duplicate_subscribe():
    """Subscribing the same callback twice should only register it once."""
    bus = EventBus()
    received = []
    cb = lambda et, d: received.append(d)
    bus.subscribe(EventType.SCAN_COMPLETED, cb)
    bus.subscribe(EventType.SCAN_COMPLETED, cb)
    bus.publish(EventType.SCAN_COMPLETED, "x")
    assert len(received) == 1


def test_event_type_values():
    """All expected event types exist."""
    expected = {
        "THREAT_FOUND", "SCAN_STARTED", "SCAN_COMPLETED", "FILE_SCANNED",
        "SIGNATURE_UPDATED", "QUARANTINE_ACTION", "REMEDIATION_ACTION",
        "DASHBOARD_METRIC",
    }
    actual = {et.name for et in EventType}
    assert expected == actual
