"""Tests for the AutomationEngine rule-handler dispatch registry.

Lock in the import-time invariant: every Rule subclass imported by the
engine module has a corresponding ``_handle_*`` entry in
``_RULE_HANDLERS``, and the coverage check rejects drift.
"""

from __future__ import annotations

import pytest

from ledgrab.core.automations import automation_engine
from ledgrab.core.automations.automation_engine import (
    AutomationEngine,
    _assert_rule_handler_coverage,
)
from ledgrab.storage.automation import (
    ApplicationRule,
    DisplayStateRule,
    HomeAssistantRule,
    HTTPPollRule,
    MQTTRule,
    Rule,
    StartupRule,
    SystemIdleRule,
    TimeOfDayRule,
    WebhookRule,
)

EXPECTED_RULE_TYPES = {
    StartupRule,
    ApplicationRule,
    TimeOfDayRule,
    SystemIdleRule,
    DisplayStateRule,
    MQTTRule,
    WebhookRule,
    HomeAssistantRule,
    HTTPPollRule,
}


def test_every_rule_type_has_a_handler():
    """The registry exactly covers the rule-type set the engine imports."""
    assert set(AutomationEngine._RULE_HANDLERS.keys()) == EXPECTED_RULE_TYPES


def test_handlers_are_engine_methods():
    """Each handler value is a method defined on AutomationEngine."""
    for rule_cls, handler in AutomationEngine._RULE_HANDLERS.items():
        assert callable(handler), f"handler for {rule_cls.__name__} is not callable"
        # Method names start with _handle_
        assert handler.__name__.startswith(
            "_handle_"
        ), f"handler for {rule_cls.__name__} has unexpected name {handler.__name__}"


def test_coverage_assertion_raises_when_handler_is_missing(monkeypatch):
    """Removing an entry from _RULE_HANDLERS makes the import-time check fail."""
    # Build a clone of the registry without one entry to simulate drift.
    original = dict(AutomationEngine._RULE_HANDLERS)
    pruned = {k: v for k, v in original.items() if k is not WebhookRule}
    monkeypatch.setattr(automation_engine.AutomationEngine, "_RULE_HANDLERS", pruned)

    with pytest.raises(RuntimeError, match="WebhookRule"):
        _assert_rule_handler_coverage()


def test_coverage_assertion_raises_when_unexpected_handler_added(monkeypatch):
    """An entry keyed by an unknown class is also caught."""

    class _UnknownRule(Rule):  # type: ignore[misc]
        pass

    extended = {**AutomationEngine._RULE_HANDLERS, _UnknownRule: lambda *a: True}
    monkeypatch.setattr(automation_engine.AutomationEngine, "_RULE_HANDLERS", extended)

    with pytest.raises(RuntimeError, match="_UnknownRule"):
        _assert_rule_handler_coverage()
