"""Central policy controller for SHiRE Kinect vision modes."""

import json
from pathlib import Path

from PyQt5.QtCore import QObject, pyqtSignal


class VisionModeService(QObject):
    """Controls mode transitions without owning or recording frames."""

    mode_changed = pyqtSignal(str)
    guard_changed = pyqtSignal(bool)
    status_changed = pyqtSignal(dict)

    def __init__(self, policy_path=None, parent=None):
        super().__init__(parent)

        root = Path(__file__).resolve().parent.parent
        self.policy_path = Path(
            policy_path or root / "config" / "vision_modes.json"
        )

        self.policy = self._load_policy()

        self.allowed_modes = tuple(
            self.policy["allowed_modes"]
        )

        # Fail safe after every process start.
        self.mode = self.policy["default_mode"]
        self.guard_armed = False
        self.guard_trigger = None

    def _load_policy(self):
        with self.policy_path.open(
            "r",
            encoding="utf-8",
        ) as handle:
            policy = json.load(handle)

        required = {
            "schema_version",
            "default_mode",
            "allowed_modes",
            "guard",
            "scanner",
            "design_tracking",
            "privacy",
        }

        missing = required.difference(policy)

        if missing:
            raise ValueError(
                "Vision policy missing: "
                + ", ".join(sorted(missing))
            )

        if (
            policy["default_mode"]
            not in policy["allowed_modes"]
        ):
            raise ValueError(
                "Default vision mode is not allowed."
            )

        if policy["guard"].get(
            "tailscale_funnel_allowed"
        ):
            raise ValueError(
                "Guard Mode policy must prohibit Funnel."
            )

        if (
            policy["guard"].get("backend_bind_host")
            != "127.0.0.1"
        ):
            raise ValueError(
                "Guard backend must bind to localhost."
            )

        return policy

    def set_mode(self, mode):
        mode = str(mode).strip().lower()

        if mode not in self.allowed_modes:
            raise ValueError(
                f"Unsupported vision mode: {mode}"
            )

        if mode == "guard":
            raise ValueError(
                "Use arm_guard() to enter Guard Mode."
            )

        changed = (
            self.mode != mode or self.guard_armed
        )

        self.mode = mode
        self.guard_armed = False
        self.guard_trigger = None

        if changed:
            self.mode_changed.emit(self.mode)
            self.guard_changed.emit(False)
            self.status_changed.emit(self.snapshot())

        return self.snapshot()

    def arm_guard(
        self,
        phrase,
        conversation_active=False,
    ):
        phrase = " ".join(
            str(phrase).strip().lower().split()
        )

        guard = self.policy["guard"]
        direct = set(
            guard["direct_activation_phrases"]
        )
        contextual = set(
            guard["contextual_activation_phrases"]
        )

        if phrase in direct:
            permitted = True
        elif phrase in contextual:
            permitted = bool(conversation_active)
        else:
            permitted = False

        if not permitted:
            return {
                "accepted": False,
                "reason": (
                    "Guard activation phrase was not "
                    "authorised in this context."
                ),
                **self.snapshot(),
            }

        self.mode = "guard"
        self.guard_armed = True
        self.guard_trigger = phrase

        self.mode_changed.emit(self.mode)
        self.guard_changed.emit(True)
        self.status_changed.emit(self.snapshot())

        return {
            "accepted": True,
            **self.snapshot(),
        }

    def disarm_guard(self):
        self.guard_armed = False
        self.guard_trigger = None
        self.mode = self.policy["default_mode"]

        self.mode_changed.emit(self.mode)
        self.guard_changed.emit(False)
        self.status_changed.emit(self.snapshot())

        return self.snapshot()

    def snapshot(self):
        return {
            "mode": self.mode,
            "guard_armed": self.guard_armed,
            "guard_trigger": self.guard_trigger,
            "backend_bind_host": self.policy[
                "guard"
            ]["backend_bind_host"],
            "remote_transport": self.policy[
                "guard"
            ]["remote_transport"],
            "funnel_allowed": self.policy[
                "guard"
            ]["tailscale_funnel_allowed"],
            "recording_enabled": self.policy[
                "guard"
            ]["recording_enabled_by_default"],
        }
