"""
连接优先级管理器
5种控制模式的可用性检测、优先级路由和智能降级。

机擎默认（WORKPHONE_WS_FIRST=1）：业务 execute 经 device_transport，本模块供 status/connection API 展示。

优先级（高→低）：
  1. Hook/Frida  — 经 WebSocket Agent + 设备本机 Frida
  2. Agent WebSocket — 实时双向，设备端 Termux Agent 常驻
  3. ADB 静默控制 — 运维兜底，非日常主通道
  4. AI Agent — 自然语言编排，依赖以上通道
  5. scrcpy 可视化 — 投屏+人工接管
"""

from __future__ import annotations

import asyncio
import logging
import subprocess
import time
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Any, Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)

_CACHE_TTL = 30  # seconds


class ControlMode(IntEnum):
    HOOK = 1
    AGENT_WS = 2
    ADB = 3
    AI_AGENT = 4
    SCRCPY = 5


MODE_META = {
    ControlMode.HOOK: {
        "id": "hook",
        "name": "Hook / Frida",
        "desc": "读写内部能力强，适合微信深度控制",
        "requires": "Root + Frida Server",
    },
    ControlMode.AGENT_WS: {
        "id": "agent",
        "name": "Agent WebSocket",
        "desc": "实时双向通信，设备端App常驻",
        "requires": "设备端安装工作App",
    },
    ControlMode.ADB: {
        "id": "adb",
        "name": "ADB 静默控制",
        "desc": "无需Root，部署简单，稳定兜底",
        "requires": "USB调试已开启",
    },
    ControlMode.AI_AGENT: {
        "id": "ai",
        "name": "AI Agent",
        "desc": "自然语言编排复杂任务",
        "requires": "需要底层通道(Hook/WS/ADB)之一可用",
    },
    ControlMode.SCRCPY: {
        "id": "scrcpy",
        "name": "scrcpy 可视化",
        "desc": "投屏与人工接管",
        "requires": "ADB连接 + scrcpy已安装",
    },
}


@dataclass
class ModeStatus:
    mode: ControlMode
    available: bool
    priority: int
    detail: str = ""
    meta: Dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> Dict[str, Any]:
        m = MODE_META.get(self.mode, {})
        return {
            "id": m.get("id", str(self.mode)),
            "name": m.get("name", ""),
            "desc": m.get("desc", ""),
            "requires": m.get("requires", ""),
            "priority": self.priority,
            "available": self.available,
            "detail": self.detail,
            **self.meta,
        }


class ConnectionPriorityManager:
    """对单台设备评估所有控制模式的可用性并按优先级排序。"""

    def __init__(self):
        self._scrcpy_available: Optional[bool] = None
        self._cache: Dict[str, Tuple[float, List[ModeStatus]]] = {}

    def evaluate(self, device_id: str) -> List[ModeStatus]:
        now = time.time()
        cached = self._cache.get(device_id)
        if cached and (now - cached[0]) < _CACHE_TTL:
            return cached[1]

        from services.ws_hub import ws_hub
        from services.adb_device import adb_manager

        results: List[ModeStatus] = []

        # --- Collect raw signals ---
        adb_ok, adb_serial = self._check_adb(device_id, adb_manager)
        ws_ok, ws_detail = self._check_ws(device_id, ws_hub)
        hook_ok = self._check_hook_for_device(device_id, ws_hub)
        scrcpy_ok = adb_ok and self._check_scrcpy()
        ws_first = self._ws_first_enabled()
        has_base = ws_ok or hook_ok or (adb_ok and not ws_first)

        # 1. Hook / Frida（WS 优先：Agent 上报 frida_available 或 WS ping）
        hook_detail = "Frida Server 运行中" if hook_ok else (
            "Agent WS 在线但 Frida 未 attach" if ws_ok else "未检测到 Hook 服务"
        )
        results.append(ModeStatus(
            mode=ControlMode.HOOK, available=hook_ok, priority=1,
            detail=hook_detail,
        ))

        # 2. Agent WebSocket
        results.append(ModeStatus(
            mode=ControlMode.AGENT_WS, available=ws_ok, priority=2,
            detail=ws_detail,
        ))

        # 3. ADB 静默控制（无线主控下降级，仅运维/兜底）
        adb_detail = f"ADB 已连接 ({adb_serial})" if adb_ok else "ADB 未连接（无线主控可不插线）"
        if ws_first and ws_ok and not adb_ok:
            adb_detail = "无线主控：ADB 非必需"
        results.append(ModeStatus(
            mode=ControlMode.ADB, available=adb_ok, priority=3,
            detail=adb_detail,
            meta={"adb_serial": adb_serial, "optional": ws_first} if adb_ok or ws_first else {},
        ))

        # 4. AI Agent
        results.append(ModeStatus(
            mode=ControlMode.AI_AGENT, available=has_base, priority=4,
            detail="底层通道就绪" if has_base else "需要至少一个底层通道",
        ))

        # 5. scrcpy 可视化
        results.append(ModeStatus(
            mode=ControlMode.SCRCPY, available=scrcpy_ok, priority=5,
            detail="scrcpy 可用" if scrcpy_ok else ("ADB未连接" if not adb_ok else "scrcpy 未安装"),
        ))

        results.sort(key=lambda m: m.priority)
        self._cache[device_id] = (now, results)
        return results

    # ---- individual checks ----

    @staticmethod
    def _check_ws(device_id: str, ws_hub) -> Tuple[bool, str]:
        if ws_hub.is_online(device_id):
            return True, "WebSocket 已连接"
        online_ids = list(ws_hub.connections.keys())
        if online_ids:
            return True, f"WebSocket 已连接 ({online_ids[0][:8]}…)"
        return False, "设备Agent未连接"

    @staticmethod
    def _check_adb(device_id: str, adb_manager) -> Tuple[bool, str]:
        resolved = adb_manager.resolve_serial(device_id)
        if resolved:
            dev = adb_manager.get_device(resolved)
            if dev and dev.is_online():
                adb_manager.register_mapping(device_id, dev.serial)
                return True, dev.serial
        for serial in adb_manager.scan_devices():
            dev = adb_manager.get_device(serial)
            if dev and dev.is_online():
                adb_manager.register_mapping(device_id, dev.serial)
                return True, dev.serial
        return False, ""

    def _check_hook_fast(self) -> bool:
        try:
            r = subprocess.run(
                ["frida-ps", "-U"],
                capture_output=True, text=True, timeout=3,
            )
            if r.returncode == 0 and len(r.stdout.strip().split("\n")) > 2:
                return True
        except Exception:
            pass
        return False

    @staticmethod
    def _ws_first_enabled() -> bool:
        try:
            from config import settings
            return bool(getattr(settings, "WORKPHONE_WS_FIRST", True))
        except Exception:
            return True

    def _check_hook_for_device(self, device_id: str, ws_hub) -> bool:
        """Hook 可用性：无线优先看 Agent frida_available；否则主机 frida-ps -U。"""
        if ws_hub.is_online(device_id):
            info = ws_hub.get_device_info(device_id) or {}
            if info.get("frida_available"):
                return True
        if self._ws_first_enabled():
            return False
        return self._check_hook_fast()

    def _check_scrcpy(self) -> bool:
        if self._scrcpy_available is not None:
            return self._scrcpy_available
        try:
            r = subprocess.run(["which", "scrcpy"], capture_output=True, text=True, timeout=3)
            self._scrcpy_available = r.returncode == 0
        except Exception:
            self._scrcpy_available = False
        return self._scrcpy_available

    # ---- public helpers ----

    def get_best_mode(self, device_id: str) -> Optional[ModeStatus]:
        for m in self.evaluate(device_id):
            if m.available:
                return m
        return None

    def choose_execution_channel(self, device_id: str) -> str:
        best = self.get_best_mode(device_id)
        if not best:
            return "offline"
        return MODE_META[best.mode]["id"]

    def invalidate_cache(self):
        self._cache.clear()
        self._scrcpy_available = None

    async def async_evaluate(self, device_id: str) -> List[ModeStatus]:
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, self.evaluate, device_id)


connection_priority = ConnectionPriorityManager()
