from __future__ import annotations

import json
import os
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


class MacRequiredError(RuntimeError):
    pass


class NeedConfirmationError(Exception):
    def __init__(self, action: dict[str, Any]) -> None:
        self.action = action
        super().__init__("confirmation required")


@dataclass
class DeviceResult:
    ok: bool
    message: str
    action_type: str
    audit_id: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "ok": self.ok,
            "message": self.message,
            "action_type": self.action_type,
            "audit_id": self.audit_id,
        }


class DeviceGateway:
    """Mac 设备控制网关 — open_app / shortcut 骨架。"""

    def __init__(self, config: dict[str, Any], *, audit_dir: Path | None = None) -> None:
        self._cfg = config.get("device", {})
        self._audit_dir = audit_dir

    def _is_mac(self) -> bool:
        mode = os.environ.get("NIANNIAN_MODE", "auto")
        if mode == "docker":
            return False
        if mode == "mac":
            return True
        return Path("/Applications").is_dir()

    def _audit(self, action: dict[str, Any], result: DeviceResult) -> str:
        audit_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
        if self._audit_dir:
            self._audit_dir.mkdir(parents=True, exist_ok=True)
            path = self._audit_dir / "device-audit.jsonl"
            row = {
                "id": audit_id,
                "ts": datetime.now(timezone.utc).isoformat(),
                "action": action,
                "ok": result.ok,
                "message": result.message,
            }
            with path.open("a", encoding="utf-8") as f:
                f.write(json.dumps(row, ensure_ascii=False) + "\n")
        return audit_id

    def execute(self, action: dict[str, Any], *, confirmed: bool = False) -> DeviceResult:
        if not self._cfg.get("enabled", True):
            return DeviceResult(False, "device control disabled", action.get("type", ""))
        if not self._is_mac():
            raise MacRequiredError("device control requires Mac runtime")

        action_type = str(action.get("type") or "")
        params = action.get("params") or {}
        require = set(self._cfg.get("require_confirmation") or [])
        if action_type in require and not confirmed:
            raise NeedConfirmationError(action)

        if action_type == "open_app":
            app = str(params.get("app") or "")
            allowed = self._allowed_apps()
            if allowed and app not in allowed:
                result = DeviceResult(False, f"app not allowed: {app}", action_type)
            else:
                try:
                    subprocess.run(["open", "-a", app], check=True, capture_output=True)
                    result = DeviceResult(True, f"已打开 {app}", action_type)
                except subprocess.CalledProcessError as e:
                    result = DeviceResult(False, str(e), action_type)
        elif action_type == "run_shortcut":
            name = str(params.get("shortcut") or "")
            try:
                subprocess.run(["shortcuts", "run", name], check=True, capture_output=True)
                result = DeviceResult(True, f"已运行快捷指令 {name}", action_type)
            except (FileNotFoundError, subprocess.CalledProcessError) as e:
                result = DeviceResult(False, str(e), action_type)
        else:
            result = DeviceResult(False, f"unsupported action: {action_type}", action_type)

        result.audit_id = self._audit(action, result)
        return result

    def _allowed_apps(self) -> list[str]:
        for item in self._cfg.get("allowed_actions") or []:
            if item.get("type") == "open_app":
                return list(item.get("apps") or [])
        return []
