"""Home Assistant MQTT Discovery payloads and command dispatch.

SECURITY BOUNDARY — this module is the entire remote-control API of the door panel.
Same principle as every other host's agent in this project: the local LLM never gets
a network path to this machine directly. The only chain is LLM tool call -> Home
Assistant service call -> MQTT -> this dispatcher.

This is deliberately NOT how the LLM registers a person or reads presence/weather/
groceries — those are `identity`'s and `pantry-vision`'s own published APIs, called
directly by HA or the kiosk browser, not through this agent. This agent only ever
controls which screen the physical panel is showing.
"""

from __future__ import annotations

import json
import logging
from typing import Callable

from . import __version__

log = logging.getLogger(__name__)

DISCOVERY_PREFIX = "homeassistant"


class Discovery:
    def __init__(self, client, node_id: str, friendly_name: str, room: str = ""):
        self.client = client
        self.node_id = node_id
        self.friendly_name = friendly_name
        self.room = (room or "").strip()
        self.base = f"doorpanel/{node_id}"
        self.availability_topic = f"{self.base}/availability"
        self._handlers: dict[str, Callable[[str], None]] = {}

        self.device = {
            "identifiers": [f"doorpanel_{node_id}"],
            "name": friendly_name,
            "manufacturer": "SmartestHome",
            "model": "Sway door panel",
            "sw_version": __version__,
        }
        # Which room this physically sits in, as an HA area_id. `suggested_area`
        # is honoured by HA only when the device is FIRST discovered — moving a
        # device later means moving it in HA too, this cannot un-file it. Omitted
        # entirely when unset, because an empty suggested_area is not the same
        # request as no suggestion. See docs/rooms-and-endpoints.md.
        if self.room:
            self.device["suggested_area"] = self.room

    def _publish_config(self, component: str, object_id: str, payload: dict) -> None:
        payload = {
            "availability_topic": self.availability_topic,
            "device": self.device,
            "unique_id": f"{self.node_id}_{object_id}",
            **payload,
        }
        topic = f"{DISCOVERY_PREFIX}/{component}/{self.node_id}/{object_id}/config"
        self.client.publish(topic, json.dumps(payload), qos=1, retain=True)

    def _command_topic(self, suffix: str, handler) -> str:
        topic = f"{self.base}/{suffix}"
        self._handlers[topic] = handler
        return topic

    def subscribe_all(self) -> None:
        for topic in self._handlers:
            self.client.subscribe(topic, qos=1)

    def dispatch(self, topic: str, payload: str) -> None:
        handler = self._handlers.get(topic)
        if handler is None:
            log.warning("no handler for %s", topic)
            return
        try:
            handler(payload)
        except Exception:
            log.exception("handler for %s failed", topic)

    def publish_available(self, available: bool = True) -> None:
        self.client.publish(
            self.availability_topic,
            "online" if available else "offline",
            qos=1,
            retain=True,
        )

    def register_screens(self, on_show) -> None:
        for object_id, name, screen, icon in (
            ("show_home", "Show home", "home", "mdi:home-outline"),
            ("show_register", "Show registration", "register", "mdi:account-plus"),
        ):
            self._publish_config(
                "button",
                object_id,
                {
                    "name": name,
                    "command_topic": self._command_topic(
                        f"show/{screen}", lambda _payload, screen=screen: on_show(screen)
                    ),
                    "icon": icon,
                },
            )
