"""
多手机设备管理接口。

面向存客宝/触客宝/超管等外部系统：把单机 SDK 能力包装成可筛选、
可批量执行、可审计的 Fleet API。单机能力仍由 devices/unified 路由负责。
"""

from __future__ import annotations

import asyncio
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field

from services.device_fleet import list_merged_local_devices
from services.device_id_util import device_id_md5
from services.ws_hub import ws_hub


router = APIRouter()


WRITE_ACTIONS = {
    "send_message",
    "batch_send",
    "mass_send",
    "add_friend",
    "batch_add_friend",
    "post_moments",
    "like_moments",
    "comment_moments",
    "create_group",
    "invite_to_group",
    "remove_from_group",
    "set_group_notice",
    "set_group_name",
    "set_group_welcome",
    "delete_tag",
    "create_tag",
    "tag_add",
    "tag_remove",
}


class FleetExecuteRequest(BaseModel):
    """批量执行请求。"""

    device_ids: Optional[List[str]] = None
    project_id: Optional[str] = None
    all_online: bool = False
    platform: str = "wechat"
    action: str
    params: Dict[str, Any] = Field(default_factory=dict)
    hook_only: bool = False
    timeout: int = 60
    max_concurrency: int = 3
    dry_run: bool = False
    confirm: bool = False


def _normalize_status(device: dict) -> str:
    if ws_hub.is_online(device.get("device_id", "")):
        return "online"
    return str(device.get("status") or "offline")


async def _select_devices(
    *,
    device_ids: Optional[List[str]] = None,
    project_id: Optional[str] = None,
    all_online: bool = False,
    status: str = "",
    capability: str = "",
) -> List[dict]:
    devices = await list_merged_local_devices()
    selected = []
    wanted = set(device_ids or [])
    for device in devices:
        did = device.get("device_id", "")
        if wanted and did not in wanted:
            continue
        if project_id and str(device.get("project_id") or "") != str(project_id):
            continue
        if all_online and not ws_hub.is_online(did):
            continue
        if status and _normalize_status(device) != status:
            continue
        if capability and capability not in (device.get("capabilities") or []):
            continue
        normalized = dict(device)
        normalized["status"] = _normalize_status(normalized)
        normalized["online"] = ws_hub.is_online(did)
        normalized["device_id_md5"] = normalized.get("device_id_md5") or device_id_md5(did)
        selected.append(normalized)
    return selected


@router.get("/fleet/summary", response_model=dict)
async def fleet_summary(project_id: str = ""):
    """所有手机/项目手机总览。"""
    devices = await _select_devices(project_id=project_id or None)
    online = [d for d in devices if d.get("online")]
    adb = [d for d in devices if d.get("status") == "adb"]
    offline = [d for d in devices if not d.get("online") and d.get("status") != "adb"]
    by_project: Dict[str, int] = {}
    for device in devices:
        pid = str(device.get("project_id") or "")
        by_project[pid] = by_project.get(pid, 0) + 1
    return {
        "code": 200,
        "data": {
            "total": len(devices),
            "online": len(online),
            "adb": len(adb),
            "offline": len(offline),
            "by_project": by_project,
            "device_ids": [d.get("device_id") for d in devices],
            "online_device_ids": [d.get("device_id") for d in online],
        },
    }


@router.get("/fleet/devices", response_model=dict)
async def fleet_devices(
    project_id: str = "",
    status: str = "",
    capability: str = "",
    online_only: bool = False,
):
    """按项目、状态、能力筛选设备列表。"""
    devices = await _select_devices(
        project_id=project_id or None,
        status=status,
        capability=capability,
        all_online=online_only,
    )
    return {"code": 200, "data": {"devices": devices, "count": len(devices)}}


@router.post("/fleet/execute", response_model=dict)
async def fleet_execute(req: FleetExecuteRequest):
    """在多台在线手机上批量执行同一个设备动作。"""
    devices = await _select_devices(
        device_ids=req.device_ids,
        project_id=req.project_id,
        all_online=req.all_online or not req.device_ids,
        status="online",
    )
    if not devices:
        raise HTTPException(status_code=404, detail="没有匹配的在线设备")

    is_write = req.action in WRITE_ACTIONS
    if is_write and len(devices) > 1 and not (req.dry_run or req.confirm):
        return {
            "code": 200,
            "data": {
                "success": False,
                "confirm_required": True,
                "reason": "批量写类动作需要 confirm=true；可先 dry_run=true 查看目标设备",
                "action": req.action,
                "target_count": len(devices),
                "targets": [d.get("device_id") for d in devices],
            },
        }
    if req.dry_run:
        return {
            "code": 200,
            "data": {
                "success": True,
                "dry_run": True,
                "action": req.action,
                "target_count": len(devices),
                "targets": [d.get("device_id") for d in devices],
            },
        }

    sem = asyncio.Semaphore(max(1, min(int(req.max_concurrency or 1), 10)))
    timeout = max(5, min(int(req.timeout or 60), 300))

    async def run_one(device: dict) -> dict:
        did = device.get("device_id", "")
        async with sem:
            try:
                result = await ws_hub.send_command(
                    did,
                    {
                        "type": "execute",
                        "data": {
                            "script": req.platform,
                            "action": req.action,
                            "params": req.params or {},
                            "hook_only": req.hook_only,
                        },
                    },
                    timeout=timeout,
                )
                return {
                    "device_id": did,
                    "device_id_md5": device_id_md5(did),
                    "success": result.get("code") == 200,
                    "result": result,
                }
            except Exception as exc:
                return {
                    "device_id": did,
                    "device_id_md5": device_id_md5(did),
                    "success": False,
                    "error": str(exc),
                }

    results = await asyncio.gather(*(run_one(device) for device in devices))
    ok = sum(1 for item in results if item.get("success"))
    return {
        "code": 200,
        "data": {
            "success": ok == len(results),
            "action": req.action,
            "target_count": len(results),
            "success_count": ok,
            "failed_count": len(results) - ok,
            "results": results,
        },
    }
