"""Unit tests for the Home Assistant event parser.

These tests don't need a database or HA server — the parser is a pure
function from ``ha_event_dict`` to :class:`ServiceEvent`.
"""

from __future__ import annotations

from notify_bridge_core.models.events import EventType
from notify_bridge_core.providers.base import ServiceProviderType
from notify_bridge_core.providers.home_assistant.event_parser import parse_event


def _ha_event_envelope(event_type: str, data: dict) -> dict:
    return {
        "event_type": event_type,
        "data": data,
        "time_fired": "2026-05-13T12:34:56.789Z",
    }


def test_state_changed_basic() -> None:
    payload = _ha_event_envelope(
        "state_changed",
        {
            "entity_id": "binary_sensor.front_door",
            "old_state": {"state": "off", "attributes": {}},
            "new_state": {
                "state": "on",
                "attributes": {
                    "friendly_name": "Front Door",
                    "device_class": "door",
                },
            },
        },
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.event_type is EventType.HA_STATE_CHANGED
    assert evt.provider_type is ServiceProviderType.HOME_ASSISTANT
    assert evt.collection_id == "binary_sensor.front_door"
    assert evt.collection_name == "Front Door"
    assert evt.extra["old_state"] == "off"
    assert evt.extra["new_state"] == "on"
    assert evt.extra["domain"] == "binary_sensor"
    assert evt.extra["device_class"] == "door"
    # Area was not provided in lookup -> None.
    assert evt.extra["area"] is None


def test_state_changed_with_area_lookup() -> None:
    payload = _ha_event_envelope(
        "state_changed",
        {
            "entity_id": "light.kitchen",
            "old_state": {"state": "off", "attributes": {}},
            "new_state": {
                "state": "on",
                "attributes": {"friendly_name": "Kitchen Light"},
            },
        },
    )
    evt = parse_event(
        payload,
        provider_name="HA",
        area_lookup={"light.kitchen": "Kitchen"},
    )
    assert evt is not None
    assert evt.extra["area"] == "Kitchen"


def test_state_changed_entity_removed() -> None:
    """new_state=None means HA removed the entity. Surface as 'removed' so
    templates can branch on it; collection_name falls back to old_state."""
    payload = _ha_event_envelope(
        "state_changed",
        {
            "entity_id": "sensor.dropped",
            "old_state": {
                "state": "online",
                "attributes": {"friendly_name": "Dropped Sensor"},
            },
            "new_state": None,
        },
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.extra["new_state"] == "removed"
    assert evt.collection_name == "Dropped Sensor"


def test_automation_triggered() -> None:
    payload = _ha_event_envelope(
        "automation_triggered",
        {
            "name": "Front door notification",
            "entity_id": "automation.front_door_notify",
            "source": "state of binary_sensor.front_door",
        },
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.event_type is EventType.HA_AUTOMATION_TRIGGERED
    assert evt.collection_name == "Front door notification"
    assert evt.collection_id == "automation.front_door_notify"
    assert evt.extra["automation_name"] == "Front door notification"
    assert evt.extra["trigger_source"] == "state of binary_sensor.front_door"


def test_call_service_with_target() -> None:
    payload = _ha_event_envelope(
        "call_service",
        {
            "domain": "light",
            "service": "turn_on",
            "service_data": {"entity_id": "light.kitchen"},
        },
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.event_type is EventType.HA_SERVICE_CALLED
    assert evt.collection_id == "light.turn_on"
    assert evt.extra["target_entity"] == "light.kitchen"
    assert evt.extra["service_domain"] == "light"
    assert evt.extra["service_name"] == "turn_on"


def test_call_service_with_multi_target() -> None:
    """When the call hits multiple entities, the parser comma-joins them
    so templates can render ``{{ target_entity }}`` without iterating."""
    payload = _ha_event_envelope(
        "call_service",
        {
            "domain": "light",
            "service": "turn_off",
            "service_data": {
                "entity_id": ["light.kitchen", "light.living_room"],
            },
        },
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.extra["target_entity"] == "light.kitchen, light.living_room"


def test_generic_event_fallback() -> None:
    """Any event_type not in the known set becomes ha_event_fired with the
    raw event_type stashed in extras so loud catch-all subscriptions work."""
    payload = _ha_event_envelope(
        "custom_event_xyz",
        {"foo": "bar"},
    )
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.event_type is EventType.HA_EVENT_FIRED
    assert evt.extra["ha_event_type"] == "custom_event_xyz"
    assert evt.extra["event_data"] == {"foo": "bar"}


def test_malformed_payload_returns_none() -> None:
    assert parse_event({}, provider_name="HA") is None
    assert parse_event("not a dict", provider_name="HA") is None  # type: ignore[arg-type]
    # state_changed without entity_id is unrecoverable
    bad = _ha_event_envelope("state_changed", {"new_state": None})
    assert parse_event(bad, provider_name="HA") is None
    # call_service without domain/service is unrecoverable
    bad2 = _ha_event_envelope("call_service", {"service": "turn_on"})
    assert parse_event(bad2, provider_name="HA") is None


def test_time_fired_iso_with_z_suffix_parses() -> None:
    """HA uses ``Z`` suffix; older Python ``fromisoformat`` rejects it.
    The parser must handle both forms or we'd lose the timestamp."""
    from datetime import timezone
    payload = _ha_event_envelope(
        "state_changed",
        {
            "entity_id": "sensor.temp",
            "old_state": {"state": "20", "attributes": {}},
            "new_state": {"state": "21", "attributes": {}},
        },
    )
    payload["time_fired"] = "2026-05-13T12:34:56.789Z"
    evt = parse_event(payload, provider_name="HA")
    assert evt is not None
    assert evt.timestamp.tzinfo is not None
    assert evt.timestamp.utcoffset() == timezone.utc.utcoffset(None)
