"""Home Assistant service provider — WebSocket subscription based.

Unlike polling providers (Immich, NUT, Google Photos) and webhook providers
(Gitea, Planka), the HA provider maintains a long-lived WebSocket connection
to the HA server and pushes events into the dispatch pipeline as they
arrive. The lifecycle is owned by the server-side subscription manager
(see ``services/ha_subscription.py``).
"""

from __future__ import annotations

import logging
from typing import Any, Callable

import aiohttp

from notify_bridge_core.models.events import ServiceEvent
from notify_bridge_core.providers.base import (
    EventEmitCallback,
    ServiceProvider,
    ServiceProviderType,
)
from notify_bridge_core.templates.variables import TemplateVariableDefinition

from .client import HomeAssistantWSClient
from .event_parser import parse_event


# Status callback signature: ``(state, detail)`` where ``state`` is one of
# ``"connected"`` / ``"disconnected"`` and ``detail`` is an optional already-
# redacted reason string (or None on connect).
StatusChangeCallback = Callable[[str, str | None], None]

_LOGGER = logging.getLogger(__name__)


# Home Assistant template variables exposed to Jinja2.
HOME_ASSISTANT_VARIABLES: list[TemplateVariableDefinition] = [
    TemplateVariableDefinition(
        name="entity_id",
        type="string",
        description="HA entity id (e.g. light.kitchen)",
        example="light.kitchen",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="friendly_name",
        type="string",
        description="Human-readable entity name from attributes.friendly_name",
        example="Kitchen Light",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="domain",
        type="string",
        description="HA domain prefix of the entity (e.g. light, sensor, binary_sensor)",
        example="light",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="old_state",
        type="string",
        description="Previous state string before the change",
        example="off",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="new_state",
        type="string",
        description="New state string (literal 'removed' when entity was deleted)",
        example="on",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="attributes",
        type="dict",
        description="Full attributes dict of the new state",
        example='{"brightness": 255, "color_mode": "brightness"}',
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="device_class",
        type="string",
        description="Device class from attributes (motion, door, temperature, ...)",
        example="motion",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="unit_of_measurement",
        type="string",
        description="Unit suffix for numeric sensors",
        example="°C",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="area",
        type="string",
        description="Area name from the HA area registry (empty when not assigned)",
        example="Kitchen",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="last_changed",
        type="string",
        description="ISO timestamp of last state change",
        example="2026-05-13T12:34:56.789+00:00",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="last_updated",
        type="string",
        description="ISO timestamp of last attribute or state update",
        example="2026-05-13T12:34:56.789+00:00",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="automation_name",
        type="string",
        description="Automation name (automation_triggered events)",
        example="Front Door Notification",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="trigger_source",
        type="string",
        description="Why an automation fired (automation_triggered events)",
        example="state of binary_sensor.front_door",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="service_called",
        type="string",
        description="Qualified service name (call_service events)",
        example="light.turn_on",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="service_domain",
        type="string",
        description="Service domain (call_service events)",
        example="light",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="service_name",
        type="string",
        description="Service name within domain (call_service events)",
        example="turn_on",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="service_data",
        type="dict",
        description="Service payload (call_service events)",
        example='{"entity_id": "light.kitchen"}',
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="target_entity",
        type="string",
        description="entity_id targeted by a service call (comma-joined for multi-target)",
        example="light.kitchen",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="ha_event_type",
        type="string",
        description="Raw HA event_type (state_changed, automation_triggered, ...)",
        example="state_changed",
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
    TemplateVariableDefinition(
        name="event_data",
        type="dict",
        description="Raw event data (generic event_fired events)",
        example='{"key": "value"}',
        provider_type=ServiceProviderType.HOME_ASSISTANT,
    ),
]


# Default event types subscribed to when the user does not override. Only
# state_changed is on by default — the others are loud and opt-in via the
# tracking-config event checkboxes.
DEFAULT_HA_EVENT_TYPES: tuple[str, ...] = ("state_changed",)


class HomeAssistantServiceProvider(ServiceProvider):
    """Home Assistant WebSocket subscription provider."""

    provider_type = ServiceProviderType.HOME_ASSISTANT
    supports_subscription = True

    def __init__(
        self,
        session: aiohttp.ClientSession,
        url: str,
        access_token: str,
        verify_tls: bool = True,
        event_types: list[str] | None = None,
        name: str = "Home Assistant",
    ) -> None:
        self._client = HomeAssistantWSClient(
            session=session,
            base_url=url,
            access_token=access_token,
            verify_tls=verify_tls,
        )
        self._name = name
        self._event_types = list(event_types) if event_types else list(DEFAULT_HA_EVENT_TYPES)
        # ``_area_lookup`` is refreshed on every (re)connect by run_subscription's
        # ``refresh_areas`` hook so the parser can enrich state_changed events
        # with the current area name.
        self._area_lookup: dict[str, str] = {}

    @property
    def client(self) -> HomeAssistantWSClient:
        return self._client

    async def connect(self) -> bool:
        ok, _ = await self._client.test_connection()
        return ok

    async def disconnect(self) -> None:
        # Session lifecycle is managed by the caller; the WS connection is
        # owned by run_subscription which exits on cancel.
        return None

    async def poll(
        self,
        collection_ids: list[str],
        tracker_state: dict[str, Any],
    ) -> tuple[list[ServiceEvent], dict[str, Any]]:
        # Subscription-based ingest. The polling scheduler MUST NOT call us
        # — the subscription manager owns this provider's lifecycle instead.
        return [], tracker_state

    async def subscribe(
        self,
        emit: EventEmitCallback,
        on_status_change: StatusChangeCallback | None = None,
    ) -> None:
        async def _on_event(ha_event: dict[str, Any]) -> None:
            event = parse_event(
                ha_event,
                provider_name=self._name,
                area_lookup=self._area_lookup,
            )
            if event is None:
                return
            await emit(event)

        async def _refresh_areas() -> dict[str, str]:
            try:
                self._area_lookup = await self._client.get_entity_to_area_lookup()
            except Exception:  # noqa: BLE001
                # Best-effort: keep the previous lookup on failure.
                _LOGGER.exception("Failed to refresh HA area lookup")
            return self._area_lookup

        await self._client.run_subscription(
            on_event=_on_event,
            event_types=self._event_types,
            refresh_areas=_refresh_areas,
            on_status_change=on_status_change,
        )

    def get_available_variables(self) -> list[TemplateVariableDefinition]:
        return list(HOME_ASSISTANT_VARIABLES)

    def get_provider_config_schema(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "url": {
                    "type": "string",
                    "description": "Home Assistant base URL (http://homeassistant.local:8123)",
                    "example": "http://homeassistant.local:8123",
                },
                "access_token": {
                    "type": "string",
                    "description": "Long-lived access token (HA Profile -> Long-Lived Access Tokens)",
                    "secret": True,
                },
                "verify_tls": {
                    "type": "boolean",
                    "description": "Validate TLS certificate. Disable only for self-signed HA setups on trusted networks.",
                    "default": True,
                },
                "event_types": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "HA event types to subscribe to. Defaults to ['state_changed'].",
                    "default": list(DEFAULT_HA_EVENT_TYPES),
                },
            },
            "required": ["url", "access_token"],
        }

    async def list_collections(self) -> list[dict[str, Any]]:
        """Return the current entity list for the entity-picker UI."""
        try:
            states = await self._client.get_states()
        except Exception as err:  # noqa: BLE001
            _LOGGER.warning("Could not fetch HA states: %s", err)
            return []
        out: list[dict[str, Any]] = []
        for state in states:
            entity_id = state.get("entity_id")
            if not isinstance(entity_id, str):
                continue
            attrs = state.get("attributes") or {}
            out.append({
                "id": entity_id,
                "name": attrs.get("friendly_name") or entity_id,
                "state": state.get("state"),
                "domain": entity_id.split(".", 1)[0] if "." in entity_id else "",
            })
        return out

    async def test_connection(self) -> dict[str, Any]:
        ok, message = await self._client.test_connection()
        return {"ok": ok, "message": message}
