"""Smart home routine manager demo integrating MQTT."""

from __future__ import annotations

import json
import re
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple

from omnilink import (
    AgentFeedback,
    AgentQuestion,
    OmniLinkEngine,
    OmniLinkMQTTBridge,
    TypeRegistry,
)

from .api import (
    get_summary,
    lock_door,
    set_thermostat_mode,
    set_thermostat_temperature,
    turn_off_light,
)
from .use_case import build_use_case

RoutineResult = Tuple[List[Dict[str, Any]], Dict[str, Any]]
RoutineRunner = Callable[[], RoutineResult]


@dataclass(frozen=True)
class RoutineDefinition:
    """Describe how a smart-home routine should be executed."""

    key: str
    label: str
    runner: RoutineRunner
    requires_confirmation: bool = False
    confirmation_prompt: Optional[str] = None
    confirmation_timeout: float = 30.0
    accepted_answers: Sequence[str] = ("yes", "approve", "approved", "y")


_SECURE_ROUTINE_KEY = "secure_house_for_night"


def _execute_secure_house_for_night() -> RoutineResult:
    """Return actions and summary for the nightly security routine."""

    actions: List[Dict[str, Any]] = []
    rooms = ("living_room", "kitchen", "bedroom", "office")
    doors = ("front_door", "garage_door", "back_door")

    for room in rooms:
        actions.append({"action": "turn_off_light", "room": room, "result": turn_off_light(room)})

    for door in doors:
        actions.append({"action": "lock_door", "door": door, "result": lock_door(door)})

    actions.append(
        {
            "action": "set_thermostat_mode",
            "mode": "auto",
            "result": set_thermostat_mode("auto"),
        }
    )
    actions.append(
        {
            "action": "set_thermostat_temperature",
            "value": 19.0,
            "result": set_thermostat_temperature(19.0),
        }
    )

    summary = get_summary()
    return actions, summary


_ROUTINES: Dict[str, RoutineDefinition] = {
    _SECURE_ROUTINE_KEY: RoutineDefinition(
        key=_SECURE_ROUTINE_KEY,
        label="Secure house for night",
        runner=_execute_secure_house_for_night,
        requires_confirmation=True,
        confirmation_prompt="Secure the house for the night?",
    )
}

_ROUTINE_ALIASES: Dict[str, str] = {
    _SECURE_ROUTINE_KEY: _SECURE_ROUTINE_KEY,
    "secure_the_house_for_the_night": _SECURE_ROUTINE_KEY,
    "nightly_lockdown": _SECURE_ROUTINE_KEY,
}

_STATIC_ROUTINE_TEMPLATES: Dict[str, str] = {
    "secure_house_for_night": _SECURE_ROUTINE_KEY,
    "secure_the_house_for_the_night": _SECURE_ROUTINE_KEY,
}

_ROUTINE_PATTERNS: Sequence[str] = (
    "run_[routine:routine]",
    "run_[routine:routine]_routine",
    "run_the_[routine:routine]_routine",
    *tuple(_STATIC_ROUTINE_TEMPLATES.keys()),
)


def _clone_types(source: TypeRegistry) -> TypeRegistry:
    types = TypeRegistry()
    for name, _pattern in source.available().items():
        spec = source.get(name)
        if spec:
            types.register(name, spec[0], spec[1])
    return types


def _normalize_key(value: str) -> str:
    return value.strip().lower().replace(" ", "_")


def _resolve_routine(evt: Dict[str, Any]) -> Optional[RoutineDefinition]:
    template = evt.get("normalized_template") or ""
    if template in _STATIC_ROUTINE_TEMPLATES:
        key = _STATIC_ROUTINE_TEMPLATES[template]
        return _ROUTINES.get(key)

    raw = evt.get("vars", {}).get("routine")
    if isinstance(raw, str):
        normalized = _normalize_key(raw)
        key = _ROUTINE_ALIASES.get(normalized, normalized)
        return _ROUTINES.get(key)
    return None


def _handle_routine(evt: Dict[str, Any]) -> Dict[str, Any]:
    routine = _resolve_routine(evt)
    if routine is None:
        return {"ack": False, "error": "unknown_routine"}

    meta = evt.setdefault("meta", {})
    meta["routine_key"] = routine.key

    messenger = evt.get("messenger")
    if routine.requires_confirmation:
        if messenger is None:
            raise RuntimeError(
                f"Routine '{routine.label}' requires an agent messenger for confirmation"
            )
        messenger.acknowledge(
            "pending",
            message=f"awaiting operator approval for {routine.label.lower()}",
            data={"routine": routine.key},
        )
        prompt = routine.confirmation_prompt or f"Approve running {routine.label.lower()}?"
        pending = messenger.ask_question(
            AgentQuestion(
                prompt=prompt,
                choices=["yes", "no"],
                data={"routine": routine.key},
            )
        )
        try:
            reply = pending.wait(timeout=routine.confirmation_timeout, cancel_on_timeout=True)
        except TimeoutError:
            messenger.send_feedback(
                AgentFeedback(
                    message=f"Timed out waiting for {routine.label.lower()} approval",
                    kind="error",
                    ok=False,
                    data={"routine": routine.key},
                )
            )
            meta["routine_cancelled"] = "timeout"
            return {"ack": False, "error": "confirmation_timeout", "routine": routine.key}

        answer = str(reply.get("answer", "")).strip().lower()
        if answer not in routine.accepted_answers:
            messenger.send_feedback(
                AgentFeedback(
                    message=f"Operator rejected {routine.label.lower()}",
                    kind="warning",
                    ok=False,
                    data={"routine": routine.key, "reply": reply},
                )
            )
            meta["routine_cancelled"] = "rejected"
            return {
                "ack": False,
                "error": "operator_rejected",
                "routine": routine.key,
                "reply": reply,
            }

        messenger.acknowledge(
            "approved",
            message=f"operator approved {routine.label.lower()}",
            data={"routine": routine.key, "reply": reply},
        )

    actions, summary = routine.runner()
    meta["routine_summary"] = summary

    if messenger is not None:
        messenger.acknowledge(
            "completed",
            message=f"completed {routine.label.lower()}",
            data={"routine": routine.key, "actions": actions, "summary": summary},
        )
        messenger.send_feedback(
            AgentFeedback(
                message=f"Finished {routine.label.lower()}",
                kind="success",
                ok=True,
                data={"routine": routine.key, "actions": actions, "summary": summary},
            )
        )

    return {
        "ack": True,
        "routine": routine.key,
        "actions": actions,
        "summary": summary,
    }


def build_routine_engine() -> OmniLinkEngine:
    """Return an engine configured with smart-home routines and handlers."""

    use_case = build_use_case()
    types = _clone_types(use_case.types)
    escaped = [re.escape(name) for name in sorted(_ROUTINE_ALIASES.keys())]
    routine_pattern = "|".join(escaped)
    types.register("routine", f"(?:{routine_pattern})")

    templates: List[str] = list(use_case.templates)
    templates.extend(_ROUTINE_PATTERNS)

    engine = OmniLinkEngine(templates, types=types)
    use_case.configure(engine)
    engine.on(lambda evt: _resolve_routine(evt) is not None, _handle_routine)
    return engine


def _install_publishers(
    engine: OmniLinkEngine,
    *,
    mqtt_bridge: Optional[OmniLinkMQTTBridge],
) -> None:
    def _publisher(evt: Dict[str, Any]) -> None:
        meta = evt.get("meta") or {}
        summary = meta.pop("routine_summary", None)
        routine_key = meta.get("routine_key")
        if not summary:
            return

        payload = json.dumps({"routine": routine_key, "summary": summary})
        if mqtt_bridge is not None:
            try:
                mqtt_bridge.publish_context(payload)
                mqtt_bridge.publish_state_snapshot(history_limit=5)
            except Exception:
                pass

    engine.after(_publisher)


def start_smart_home_routines_demo(
    *,
    mqtt_options: Optional[Dict[str, Any]] = None,
    start_mqtt: bool = False,
) -> Tuple[OmniLinkEngine, OmniLinkMQTTBridge]:
    """Configure the smart-home routine engine with an MQTT bridge."""

    engine = build_routine_engine()

    mqtt_kwargs = dict(mqtt_options or {})
    mqtt_bridge = OmniLinkMQTTBridge(engine, **mqtt_kwargs)
    if start_mqtt:
        mqtt_bridge.start()

    _install_publishers(engine, mqtt_bridge=mqtt_bridge)
    return engine, mqtt_bridge


def trigger_secure_house_routine(engine: OmniLinkEngine) -> Dict[str, Any]:
    """Convenience helper to run the nightly security routine locally."""

    return engine.handle("secure house for night")


__all__ = [
    "RoutineDefinition",
    "build_routine_engine",
    "start_smart_home_routines_demo",
    "trigger_secure_house_routine",
]
