"""
设备统一传输层 — 机擎默认强制规则：WebSocket 无线主控优先。

所有设备控制（hook/execute、message、probe、Skill）须经本模块选路与下发。
禁止在业务路由中绕过本层直接调用 ADB / 主机 frida-ps / Mac ADB forward Agent。

优先级（WORKPHONE_WS_FIRST=1，默认）：
  1. WebSocket Agent + 设备本机 Frida（Hook）
  2. ADB 静默（仅运维兜底或显式 u2 动作）
  3. scrcpy / AI Agent 编排（依赖底层通道）

真源文档：开发文档/5、接口/02-业务对接/WebSocket无线主控口径.md
"""

from __future__ import annotations

import logging
from enum import Enum
from typing import Any, Dict, Optional

from fastapi import HTTPException

logger = logging.getLogger(__name__)


class TransportChannel(str, Enum):
    WEBSOCKET = "websocket"
    HOOK = "hook"
    ADB = "adb"
    OFFLINE = "offline"


# 允许在无 WS 时降级 ADB 的微信动作（客服解封等，见 unified.WECHAT_U2_ONLY_ACTIONS）
# 登录/退出/注册/切号属纯 UI 流程：Frida RPC 仅广播 intent，设备无接收端时返回
# no_receiver_registered（真机铁律禁止假成功），必须放行 u2/ADB 真实 UI 兜底，否则功能不可用。
WECHAT_U2_ONLY_ACTIONS = frozenset({
    "unblock_via_customer_service",
    "unblock_account",
    "unblock_self",
    "unblock_appeal",
    "unblock_with_sms",
    "set_nickname",
    "set_signature",
    "set_avatar",
    "set_sex",
    "set_region",
    "set_what_up",
    "change_password",
    "bind_phone",
    "unbind_phone",
    "get_login_devices",
    "remove_login_device",
    "enable_fingerprint",
    "set_account_protection",
    "login_by_password",
    "login_by_sms",
    "register_account",
    "auto_register",
    "logout",
    "switch_account",
    "ensure_logged_in",
    "send_red_packet",
    "receive_red_packet",
    "send_transfer",
    "receive_transfer",
    "receive_payment",
    "get_wallet_balance",
    "get_transaction_history",
    # UI/展示类素材（Frida 仅 intent 占位，须 u2 真导航+截图）
    "generate_my_qr_code",
    "show_my_qr",
    "generate_group_qr_code",
    "show_payment_code",
    "scan_qr_code",
    "add_friend_by_qr",
    "voice_call",
    "video_call",
    "share_real_time_location",
    "download_file",
    "mass_send",
    "batch_send",
    "add_favorite",
    "delete_favorite",
    "follow_official_account",
    "unfollow_official_account",
    "open_mini_program",
    "get_recent_mini_programs",
    "share_mini_program",
    "send_voice",
    "send_file",
    "send_location",
    "send_card",
    "send_emoji",
    "add_custom_emoji",
    "add_to_float",
    "remove_from_float",
    "set_privacy",
    "set_notification",
    "clear_chat_history",
    "set_chat_background",
    "set_do_not_disturb",
    "pin_chat",
    "clear_cache",
    "check_for_update",
    "delete_moments",
    "browse_channels",
    "like_channel_video",
    "comment_channel_video",
    "follow_channel",
    "unfollow_channel",
    "share_channel_video",
    # forward_message / forward_multiple / revoke_message 已实现真 Frida RPC
    # （文本转发复用 _sendMessageInternal / 撤回 DB 资格预检），走 Frida 主通道，不再强制 u2
    "get_official_account_articles",
    "reply_comment",
})


class DeviceTransport:
    """单设备传输策略与 WS 下发封装。"""

    @staticmethod
    def _settings():
        from config import settings
        return settings

    def ws_first_enabled(self) -> bool:
        return bool(getattr(self._settings(), "WORKPHONE_WS_FIRST", True))

    def ws_hook_only(self, platform: str) -> bool:
        if platform != "wechat":
            return self.ws_first_enabled()
        return bool(getattr(self._settings(), "WECHAT_WS_HOOK_ONLY", True))

    def host_adb_probe_enabled(self) -> bool:
        return bool(getattr(self._settings(), "WORKPHONE_HOST_ADB_PROBE", False))

    def is_ws_online(self, device_id: str) -> bool:
        from services.ws_hub import ws_hub
        return ws_hub.is_online(device_id)

    def resolve_mode(self, device_id: str, platform: str = "wechat", action: str = "") -> str:
        """
        解析当前设备应使用的传输模式。
        无线主控：WS 在线 → websocket；否则 offline（u2 专属动作除外）。
        """
        if self.is_ws_online(device_id):
            return TransportChannel.WEBSOCKET.value

        if self.ws_first_enabled():
            if platform == "wechat" and action in WECHAT_U2_ONLY_ACTIONS:
                from services.adb_device import adb_manager
                dev = adb_manager.get_device(device_id)
                if dev and dev.is_online():
                    return TransportChannel.ADB.value
            return TransportChannel.OFFLINE.value

        from services.adb_device import adb_manager
        adb_dev = adb_manager.get_device(device_id)
        if adb_dev and adb_dev.is_online():
            return TransportChannel.ADB.value

        from services.connection_priority import connection_priority
        ch = connection_priority.choose_execution_channel(device_id)
        if ch == "agent":
            return TransportChannel.WEBSOCKET.value
        if ch and ch != "offline":
            return ch
        return TransportChannel.OFFLINE.value

    def check_device_online(self, device_id: str, platform: str = "wechat", action: str = "") -> str:
        """按策略检查设备是否可执行业务；不可用时抛 HTTPException。"""
        mode = self.resolve_mode(device_id, platform, action)
        if mode == TransportChannel.OFFLINE.value:
            detail = (
                f"设备 WebSocket 未连接: {device_id}。"
                "无线主控请在手机 Termux 启动 Agent 连接 ws://<sdk>/ws/device/{device_id}"
            )
            if self.ws_first_enabled():
                raise HTTPException(status_code=503, detail=detail)
            raise HTTPException(status_code=503, detail=f"设备不在线: {device_id}")
        return "agent" if mode == TransportChannel.WEBSOCKET.value else mode

    def offline_payload(self, device_id: str, reason: str = "") -> Dict[str, Any]:
        msg = reason or (
            "WebSocket Agent 未在线，请启动设备端 Termux Agent "
            f"连接 ws://<sdk>/ws/device/{device_id}"
        )
        return {
            "code": 503,
            "success": False,
            "message": msg,
            "transport": TransportChannel.WEBSOCKET.value,
            "_channel_used": "websocket/offline",
        }

    def policy_meta(self, device_id: str) -> Dict[str, Any]:
        from services.device_id_util import device_id_md5
        from services.ws_hub import ws_hub

        info = ws_hub.get_device_info(device_id) or {}
        return {
            "device_id": device_id,
            "device_id_md5": device_id_md5(device_id),
            "transport": TransportChannel.WEBSOCKET.value if self.is_ws_online(device_id) else TransportChannel.OFFLINE.value,
            "ws_first": self.ws_first_enabled(),
            "ws_online": self.is_ws_online(device_id),
            "frida_available": bool(info.get("frida_available")),
            "agent_on_device": bool(info.get("on_device") or info.get("WP_AGENT_ON_DEVICE")),
        }

    async def execute_via_ws(
        self,
        device_id: str,
        platform: str,
        action: str,
        params: dict,
        *,
        timeout: int = 120,
        hook_only: bool = False,
    ) -> Dict[str, Any]:
        """经 WebSocket 统一下发 execute（唯一推荐业务入口）。"""
        from services.ws_hub import ws_hub

        # 微信内部动作优先走宝塔侧真实 Frida Session；Android/system/device
        # 仍走手机 WebSocket，避免同一设备 ID 的两个 Agent 抢占连接。
        if platform == "wechat":
            from services.server_frida_bridge import server_frida_bridge
            if server_frida_bridge.enabled_for(device_id):
                return await server_frida_bridge.execute(device_id, action, params)

        if not self.is_ws_online(device_id):
            return self.offline_payload(device_id)

        result = await ws_hub.send_command(
            device_id,
            {
                "type": "execute",
                "data": {
                    "script": platform,
                    "action": action,
                    "params": params,
                    "hook_only": hook_only,
                },
            },
            timeout=timeout,
        )
        payload = result.get("data") if isinstance(result.get("data"), dict) else {}
        agent_channel = (
            result.get("channel")
            or payload.get("channel")
            or ("hook" if hook_only and payload.get("success") else "u2")
        )
        result["_channel_used"] = f"websocket/{agent_channel}"
        result.setdefault("transport", TransportChannel.WEBSOCKET.value)
        return result

    def should_force_hook_only(self, platform: str, action: str, hook_only: bool) -> bool:
        if hook_only:
            return True
        if platform == "wechat" and getattr(self._settings(), "WECHAT_BACKEND_ONLY", True):
            return action not in WECHAT_U2_ONLY_ACTIONS
        return False


device_transport = DeviceTransport()
