"""
对外接口统一清单（Integration Manifest）

目标：把工作手机 SDK 所有 /api/v3/* 对外接口，按「模块 + 消费方」自动归类，
生成一份机器可读、始终与代码同步的接口总清单，供：
  - 存客宝（cunkebao）          直接对接业务/线索/Hook 事件接口
  - 超级管理端（superadmin）    切换连接方案 / 配置开关 / 全局管控
  - AI 数字员工（ai_employee）  通过 OpenAI / MCP / Agent 网关直接调用控机能力
  - 通用（common）              设备、健康、发现等基础接口

设计原则（模块化 + 接口化 + 清晰化）：
  - 清单从 FastAPI app.routes 动态扫描生成 → 不手工维护、不会与代码漂移
  - 模块归类按「路由前缀规则表 MODULE_RULES」判定
  - 消费方归类按「模块 → 消费方映射 MODULE_CONSUMERS」判定
  - 只读、零副作用；不依赖真机即可返回（设备相关字段会标注 online 与否）

严禁修改存客宝 / AI 数字员工 / 超级管理端代码——本模块仅暴露工作手机侧出口。
"""

from __future__ import annotations

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

# ─────────────────────────────────────────────
# 一、消费方定义
# ─────────────────────────────────────────────

CONSUMER_CUNKEBAO = "cunkebao"
CONSUMER_SUPERADMIN = "superadmin"
CONSUMER_AI_EMPLOYEE = "ai_employee"
CONSUMER_COMMON = "common"

CONSUMER_LABELS = {
    CONSUMER_CUNKEBAO: "存客宝（直连业务/线索/Hook 事件）",
    CONSUMER_SUPERADMIN: "超级管理端（连接方案切换/配置开关/全局管控）",
    CONSUMER_AI_EMPLOYEE: "AI 数字员工（OpenAI/MCP/Agent 网关调用控机）",
    CONSUMER_COMMON: "通用（设备/健康/发现/经验等基础能力）",
}

# ─────────────────────────────────────────────
# 二、模块规则表：路径前缀（去掉 /api/v3）→ 模块
#    顺序敏感：先匹配更具体的前缀
# ─────────────────────────────────────────────

# (path_prefix, module_id, module_label)
MODULE_RULES: List[Tuple[str, str, str]] = [
    ("/cunke-bao", "cunkebao_link", "存客宝对接"),
    ("/customer", "cunkebao_link", "存客宝对接"),
    ("/connection/provider", "connection_switch", "连接方案可切换驱动"),
    ("/connection", "connection", "连接协议与诊断"),
    ("/stability", "connection", "连接协议与诊断"),
    ("/gateway/v1", "gateway_openai", "AI 网关 · OpenAI 兼容"),
    ("/v1/chat", "gateway_openai", "AI 网关 · OpenAI 兼容"),
    ("/gateway/mcp", "gateway_mcp", "AI 网关 · MCP 协议"),
    ("/gateway", "gateway", "AI 网关 · REST 聚合/编队"),
    ("/ai/brain", "ai_brain", "AI Brain 技能注册与调度"),
    ("/ai", "ai_agent", "AI Agent 控机"),
    ("/agent", "ai_agent", "AI Agent 控机"),
    ("/wechat", "wechat_full", "微信全量操作"),
    ("/message", "wechat_message", "微信消息"),
    ("/friend", "wechat_friend", "微信好友"),
    ("/contacts", "wechat_contacts", "微信通讯录"),
    ("/group", "wechat_group", "微信群"),
    ("/moments", "wechat_moments", "微信朋友圈"),
    ("/tag", "wechat_tag", "微信标签"),
    ("/account", "wechat_account", "微信账号/登录/解封"),
    ("/auto-register", "wechat_account", "微信账号/登录/解封"),
    ("/payment", "wechat_payment", "微信支付/转账"),
    ("/mass-send", "wechat_message", "微信消息"),
    # 微信扩展操作（资料/扫码/收藏/表情/文件/位置/通话/小程序/公众号/看一看/评论）
    ("/profile", "wechat_extra", "微信扩展操作"),
    ("/scan", "wechat_extra", "微信扩展操作"),
    ("/favorites", "wechat_extra", "微信扩展操作"),
    ("/emoji", "wechat_extra", "微信扩展操作"),
    ("/file", "wechat_extra", "微信扩展操作"),
    ("/location", "wechat_extra", "微信扩展操作"),
    ("/call", "wechat_extra", "微信扩展操作"),
    ("/chat", "wechat_extra", "微信扩展操作"),
    ("/miniprogram", "wechat_extra", "微信扩展操作"),
    ("/official-account", "wechat_extra", "微信扩展操作"),
    ("/discover", "wechat_extra", "微信扩展操作"),
    ("/comment", "wechat_extra", "微信扩展操作"),
    ("/search", "wechat_extra", "微信扩展操作"),
    ("/settings", "wechat_extra", "微信扩展操作"),
    ("/video-channel", "wechat_extra", "微信扩展操作"),
    ("/wechat-sport", "wechat_extra", "微信扩展操作"),
    ("/hook-modules", "hook_modules", "Hook 模块管理"),
    ("/modules", "hook_modules", "Hook 模块管理"),
    ("/scripts", "ai_brain", "AI Brain 技能注册与调度"),
    ("/hook", "hook", "Hook 探测/执行/动作"),
    ("/anti-ban", "anti_ban", "防风控"),
    ("/antiban", "anti_ban", "防风控"),
    ("/fleet", "fleet", "多设备 Fleet 管理"),
    ("/devices", "devices", "设备管理与控制"),
    ("/device", "devices", "设备管理与控制"),
    ("/guard-events", "devices", "设备管理与控制"),
    ("/heartbeat", "devices", "设备管理与控制"),
    ("/workbench", "console", "控制台/工作台聚合"),
    ("/process", "console", "控制台/工作台聚合"),
    ("/frida", "frida", "Frida 无线管理"),
    ("/adb", "adb", "ADB 直连（运维兜底，非主控）"),
    ("/registry", "registry", "多服务器注册中心"),
    ("/discovery", "discovery", "设备发现"),
    ("/qrcode", "qrcode", "二维码"),
    ("/voice", "voice", "语音控制"),
    ("/capture", "capture", "抓包"),
    ("/experience", "experience", "经验库"),
    ("/projects", "projects", "项目管理"),
    ("/integration", "integration", "对外接口统一清单"),
]

# 模块 → 主消费方（一个模块可服务多个消费方）
MODULE_CONSUMERS: Dict[str, List[str]] = {
    "cunkebao_link": [CONSUMER_CUNKEBAO, CONSUMER_SUPERADMIN],
    "connection_switch": [CONSUMER_SUPERADMIN, CONSUMER_CUNKEBAO],
    "connection": [CONSUMER_SUPERADMIN, CONSUMER_COMMON],
    "gateway_openai": [CONSUMER_AI_EMPLOYEE],
    "gateway_mcp": [CONSUMER_AI_EMPLOYEE],
    "gateway": [CONSUMER_AI_EMPLOYEE, CONSUMER_SUPERADMIN],
    "ai_brain": [CONSUMER_AI_EMPLOYEE, CONSUMER_SUPERADMIN],
    "ai_agent": [CONSUMER_AI_EMPLOYEE, CONSUMER_CUNKEBAO],
    "wechat_full": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_message": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_friend": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_contacts": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_group": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_moments": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_tag": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_account": [CONSUMER_CUNKEBAO, CONSUMER_SUPERADMIN],
    "wechat_extra": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "wechat_payment": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "hook": [CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE, CONSUMER_SUPERADMIN],
    "anti_ban": [CONSUMER_SUPERADMIN, CONSUMER_CUNKEBAO],
    "fleet": [CONSUMER_CUNKEBAO, CONSUMER_SUPERADMIN, CONSUMER_AI_EMPLOYEE, CONSUMER_COMMON],
    "devices": [CONSUMER_COMMON, CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE],
    "frida": [CONSUMER_SUPERADMIN, CONSUMER_COMMON],
    "adb": [CONSUMER_SUPERADMIN, CONSUMER_COMMON],
    "hook_modules": [CONSUMER_SUPERADMIN],
    "registry": [CONSUMER_SUPERADMIN, CONSUMER_COMMON],
    "discovery": [CONSUMER_COMMON, CONSUMER_SUPERADMIN],
    "qrcode": [CONSUMER_COMMON],
    "voice": [CONSUMER_AI_EMPLOYEE, CONSUMER_COMMON],
    "capture": [CONSUMER_SUPERADMIN],
    "experience": [CONSUMER_COMMON],
    "projects": [CONSUMER_SUPERADMIN, CONSUMER_COMMON],
    "console": [CONSUMER_SUPERADMIN, CONSUMER_CUNKEBAO, CONSUMER_COMMON],
    "integration": [CONSUMER_SUPERADMIN, CONSUMER_CUNKEBAO, CONSUMER_AI_EMPLOYEE, CONSUMER_COMMON],
}

MODULE_UNKNOWN = ("other", "其它/内部")

# 模块运行时依赖：
#   server  → SDK 进程在即可用（不依赖具体设备）
#   device  → 需目标设备 WebSocket 在线（u2/ADB 即可，微信类有 u2 兜底）
#   hook    → 需设备 Frida Hook 已 attach 才能发挥完整能力（否则 u2 部分降级）
MODULE_RUNTIME: Dict[str, str] = {
    "cunkebao_link": "server",
    "connection_switch": "device",
    "connection": "device",
    "gateway_openai": "device",
    "gateway_mcp": "device",
    "gateway": "device",
    "ai_brain": "device",
    "ai_agent": "device",
    "wechat_full": "hook",
    "wechat_message": "hook",
    "wechat_friend": "hook",
    "wechat_contacts": "hook",
    "wechat_group": "hook",
    "wechat_moments": "hook",
    "wechat_tag": "hook",
    "wechat_account": "device",
    "wechat_extra": "hook",
    "wechat_payment": "hook",
    "hook": "hook",
    "anti_ban": "server",
    "fleet": "device",
    "devices": "device",
    "frida": "device",
    "adb": "device",
    "hook_modules": "server",
    "registry": "server",
    "discovery": "server",
    "qrcode": "server",
    "voice": "device",
    "capture": "device",
    "experience": "server",
    "projects": "device",
    "console": "server",
    "integration": "server",
    "other": "server",
}

# 仅纳入对外清单的方法
_PUBLIC_METHODS = {"GET", "POST", "PUT", "DELETE", "PATCH"}

# 这些路径片段视为内部/非业务，不纳入对外清单
_SKIP_PREFIXES = ("/openapi", "/docs", "/redoc", "/static")


def _classify_path(path: str) -> Tuple[str, str]:
    """返回 (module_id, module_label)。path 形如 /api/v3/cunke-bao/config。"""
    rel = path
    if rel.startswith("/api/v3"):
        rel = rel[len("/api/v3"):]
    if not rel.startswith("/"):
        rel = "/" + rel
    for prefix, mod_id, label in MODULE_RULES:
        if rel == prefix or rel.startswith(prefix + "/") or rel.startswith(prefix):
            # 更严格：前缀后须是 / 或结束，避免 /tag 误匹配 /tagx
            tail = rel[len(prefix):]
            if tail == "" or tail.startswith("/"):
                return mod_id, label
    return MODULE_UNKNOWN


def _consumers_for(module_id: str) -> List[str]:
    return MODULE_CONSUMERS.get(module_id, [CONSUMER_COMMON])


def build_manifest(app) -> Dict[str, Any]:
    """
    扫描 FastAPI app 路由，生成按模块归类的对外接口清单。

    返回:
      {
        "version": "...",
        "base_path": "/api/v3",
        "total_endpoints": N,
        "consumers": {...标签...},
        "modules": [
          {"module": "cunkebao_link", "label": "存客宝对接",
           "consumers": ["cunkebao","superadmin"],
           "endpoints": [{"method","path","summary"} ...]}
        ]
      }
    """
    modules: Dict[str, Dict[str, Any]] = {}
    total = 0
    seen: set[tuple[str, str]] = set()

    def add_endpoint(path: str, method: str, summary: str = "") -> None:
        nonlocal total
        method = method.upper()
        if method not in _PUBLIC_METHODS:
            return
        if not path or any(path.startswith(p) for p in _SKIP_PREFIXES):
            return
        key = (path, method)
        if key in seen:
            return
        if path.startswith("/api/v3"):
            mod_id, label = _classify_path(path)
        elif path in ("/health", "/ready", "/"):
            mod_id, label = ("devices", "设备管理与控制") if path == "/health" else ("integration", "对外接口统一清单")
        else:
            mod_id, label = _classify_path(path)
            if mod_id == MODULE_UNKNOWN[0]:
                return
        bucket = modules.setdefault(mod_id, {
            "module": mod_id,
            "label": label,
            "consumers": _consumers_for(mod_id),
            "endpoints": [],
        })
        bucket["endpoints"].append({
            "method": method,
            "path": path,
            "summary": summary,
        })
        seen.add(key)
        total += 1

    for route in getattr(app, "routes", []):
        path = getattr(route, "path", "") or ""
        methods = getattr(route, "methods", None) or set()
        if not path or any(path.startswith(p) for p in _SKIP_PREFIXES):
            continue
        # 只纳入 /api/v3/* 与少量根级业务端点（/health 等归 common）
        public_methods = [m for m in methods if m in _PUBLIC_METHODS]
        if not public_methods:
            continue

        summary = getattr(route, "summary", "") or ""
        if not summary:
            endpoint = getattr(route, "endpoint", None)
            doc = (endpoint.__doc__ or "").strip() if endpoint else ""
            summary = doc.splitlines()[0].strip() if doc else ""

        for m in sorted(public_methods):
            add_endpoint(path, m, summary)

    # 某些运行态下 request.app.routes 可能只暴露局部路由，但 OpenAPI 已完整。
    # 用 OpenAPI paths 做只读补齐，保证接口开放清单与 /docs 可见能力一致。
    try:
        openapi = app.openapi() if hasattr(app, "openapi") else {}
        paths = openapi.get("paths", {}) if isinstance(openapi, dict) else {}
        for path, path_item in paths.items():
            if not isinstance(path_item, dict):
                continue
            for method, meta in path_item.items():
                if str(method).upper() not in _PUBLIC_METHODS:
                    continue
                summary = ""
                if isinstance(meta, dict):
                    summary = str(meta.get("summary") or meta.get("description") or "")
                add_endpoint(path, str(method), summary)
    except Exception:
        # manifest 是发现接口，不应因 OpenAPI 补齐失败影响主服务。
        pass

    # 排序：模块按 label，端点按 path
    module_list = []
    for mod in modules.values():
        mod["endpoints"].sort(key=lambda e: (e["path"], e["method"]))
        mod["endpoint_count"] = len(mod["endpoints"])
        module_list.append(mod)
    module_list.sort(key=lambda m: m["label"])

    return {
        "version": "1.0.0",
        "base_path": "/api/v3",
        "generated_from": "fastapi.routes (dynamic, always in-sync)",
        "total_endpoints": total,
        "module_count": len(module_list),
        "consumers": CONSUMER_LABELS,
        "modules": module_list,
        "rules": {
            "note": "工作手机侧出口；严禁改存客宝/AI数字员工/超管代码，仅按本清单对接。",
            "transport": "业务下发统一经 WebSocket 主控（WORKPHONE_WS_FIRST=1）",
        },
    }


def filter_by_consumer(manifest: Dict[str, Any], consumer: str) -> Dict[str, Any]:
    """从完整清单中筛出某消费方可用的模块与端点。"""
    mods = [m for m in manifest.get("modules", []) if consumer in m.get("consumers", [])]
    total = sum(m.get("endpoint_count", 0) for m in mods)
    return {
        "version": manifest.get("version"),
        "base_path": manifest.get("base_path"),
        "consumer": consumer,
        "consumer_label": CONSUMER_LABELS.get(consumer, consumer),
        "total_endpoints": total,
        "module_count": len(mods),
        "modules": mods,
    }


def list_modules(manifest: Dict[str, Any]) -> List[Dict[str, Any]]:
    """精简模块目录（不含端点明细）。"""
    return [
        {
            "module": m["module"],
            "label": m["label"],
            "consumers": m["consumers"],
            "endpoint_count": m["endpoint_count"],
            "runtime": MODULE_RUNTIME.get(m["module"], "server"),
        }
        for m in manifest.get("modules", [])
    ]


def build_capability(
    manifest: Dict[str, Any],
    device_id: str,
    online: bool,
    supports_hook: bool,
    consumer: Optional[str] = None,
) -> Dict[str, Any]:
    """
    把「接口清单（静态）」与「设备实时状态」打通，给出某设备各模块此刻是否可直接调用。

    status 取值：
      ready          可直接调用
      degraded       设备在线但 Frida 未 attach，微信类走 u2 部分降级
      offline        设备不在线，device/hook 类不可用
    """
    modules = manifest.get("modules", [])
    if consumer:
        modules = [m for m in modules if consumer in m.get("consumers", [])]

    result_modules = []
    counts = {"ready": 0, "degraded": 0, "offline": 0}
    for m in modules:
        runtime = MODULE_RUNTIME.get(m["module"], "server")
        if runtime == "server":
            status = "ready"
        elif not online:
            status = "offline"
        elif runtime == "hook":
            status = "ready" if supports_hook else "degraded"
        else:  # device
            status = "ready"
        counts[status] += 1
        result_modules.append({
            "module": m["module"],
            "label": m["label"],
            "runtime": runtime,
            "status": status,
            "endpoint_count": m["endpoint_count"],
            "consumers": m["consumers"],
        })

    return {
        "device_id": device_id,
        "consumer": consumer or "all",
        "online": online,
        "supports_hook": supports_hook,
        "summary": counts,
        "modules": result_modules,
        "legend": {
            "ready": "可直接调用",
            "degraded": "设备在线但 Frida 未 attach，微信类走 u2 部分降级",
            "offline": "设备不在线，device/hook 类不可用",
        },
    }
