from __future__ import annotations

import asyncio
import logging
import shutil

from fastapi import APIRouter

from woc_bridge.config import (
    DIAGNOSTIC_ITEMS,
    _DIAGNOSTIC_ITEM_BY_ID,
    _require_db_reader,
    _require_xdotool,
    _state,
)
from woc_bridge.db.coordinator import (
    _auto_extract_with_lock,
    _resolve_db_state,
    with_db_retry,
)
from woc_bridge.models import (
    BridgeError,
    ConnectivityResponse,
    DiagnosticItem,
    DiagnosticRunResult,
)

logger = logging.getLogger("woc-bridge")
router = APIRouter()


# ---------------------------------------------------------------------------
# 路由：GET /api/diagnostic/connectivity
# ---------------------------------------------------------------------------
@router.get("/api/diagnostic/connectivity", response_model=ConnectivityResponse)
async def diagnostic_connectivity() -> ConnectivityResponse:
    """bridge 连通性检查（channels ProbeableAdapter 调用）。

    MVP 阶段恒返回 reachable=true / latency_ms=0 / error=null。
    真实实现可加：内部 health ping / 依赖项检查 / 平均响应时延。

    Returns:
        ConnectivityResponse：reachable / latency_ms / error
    """
    return ConnectivityResponse(reachable=True, latency_ms=0, error=None)


# ---------------------------------------------------------------------------
# 路由：GET /api/diagnostic/items
# ---------------------------------------------------------------------------
@router.get("/api/diagnostic/items")
async def diagnostic_items() -> dict:
    """返回诊断项定义列表。

    前端「诊断」页面用本接口渲染可执行的检查项卡片，每张卡片含：
    - check_id（POST /api/diagnostic/run/{check_id} 的路径参数）
    - name（卡片标题）/ description（卡片副标题）
    - severity（critical/error/warn/info，决定卡片颜色与是否阻断）
    - auto_repairable（true 时显示「自动修复」按钮，调 autofix 接口）

    Returns:
        {"items": [DiagnosticItem, ...]}（共 4 项：wechat_running /
        login_state / db_accessible / xdotool_available）
    """
    return {
        "items": [DiagnosticItem(**item) for item in DIAGNOSTIC_ITEMS],
    }


# ---------------------------------------------------------------------------
# 路由：POST /api/diagnostic/run/{check_id}
# ---------------------------------------------------------------------------
@router.post("/api/diagnostic/run/{check_id}", response_model=DiagnosticRunResult)
@with_db_retry
async def diagnostic_run(check_id: str) -> DiagnosticRunResult:
    """执行单个诊断项检查（channels DoctorAdapter 调用）。

    支持的 check_id：
    - wechat_running：pgrep -x wechat 检测进程，auto_repairable=true
    - login_state：detect_login_state()，区分 not_running/not_logged_in/logged_in
    - db_accessible：check_db_status() 区分 ok/not_found/encrypted/unreadable，
      encrypted 时返回 repair_plan 提示独立专项解决
    - xdotool_available：shutil.which('xdotool') 检测可执行文件

    Args:
        check_id: 路径参数，诊断项 ID（见 /api/diagnostic/items 返回）

    Returns:
        DiagnosticRunResult：含 passed / severity / message / auto_repairable
        / repair_plan（仅 db_accessible=encrypted/unreadable 时非空）

    Raises:
        BridgeError(INVALID_PARAMS): 未知 check_id（HTTP 400）

    Notes:
        - 不抛业务错误的诊断项：即使 passed=false 也返回 200 + 详细 message，
          让调用方按字段判定后续动作（如显示修复建议、调 autofix）
        - repair_plan 字段仅在 db_accessible 失败时给出建议，其他项为 None
    """
    item = _DIAGNOSTIC_ITEM_BY_ID.get(check_id)
    if item is None:
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"未知诊断项: {check_id}",
        )

    xdotool = _require_xdotool()
    db_reader = _require_db_reader()

    if check_id == "wechat_running":
        passed = await xdotool.is_wechat_running()
        message = "微信进程运行中" if passed else "微信进程未运行"
    elif check_id == "login_state":
        state = await xdotool.detect_login_state()
        passed = state == "logged_in"
        message = f"登录态: {state}"
    elif check_id == "db_accessible":
        # 统一走 _resolve_db_state（含 salt/key/pid/验证逻辑）
        db_state = await _resolve_db_state()
        if db_state.db_accessible:
            source = _state.key_cache.source or "unknown"
            msg = "微信 DB 可读" if db_state.status == "ok" \
                else f"DB 加密但已具备解密能力（key 来源: {source}）"
            return DiagnosticRunResult(
                check_id=check_id,
                passed=True,
                severity=item["severity"],
                message=msg,
                auto_repairable=item["auto_repairable"],
                repair_plan=None,
            )
        if db_state.status == "not_found":
            return DiagnosticRunResult(
                check_id=check_id,
                passed=False,
                severity=item["severity"],
                message="未在 /config 下找到微信消息 DB",
                auto_repairable=item["auto_repairable"],
                repair_plan=None,
            )
        if db_state.status == "unreadable":
            return DiagnosticRunResult(
                check_id=check_id,
                passed=False,
                severity=item["severity"],
                message="DB 文件存在但不可读（权限问题）",
                auto_repairable=item["auto_repairable"],
                repair_plan="检查 /config/xwechat_files 下 DB 文件权限",
            )
        # need_init / key_invalid 且无有效 key
        return DiagnosticRunResult(
            check_id=check_id,
            passed=False,
            severity=item["severity"],
            message="DB 已加密（SQLCipher），需密钥才能读取",
            auto_repairable=item["auto_repairable"],
            repair_plan="通过 POST /api/db/decrypt 注入 64 位 hex 密钥，或设置 WOC_DB_KEY 环境变量，或启用 WOC_DB_KEY_AUTO_EXTRACT=true 自动提取",
        )
    elif check_id == "xdotool_available":
        path = shutil.which("xdotool")
        passed = path is not None
        message = f"xdotool 路径: {path}" if passed else "xdotool 不可用"
    else:  # 理论不可达
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"未知诊断项: {check_id}",
        )

    return DiagnosticRunResult(
        check_id=check_id,
        passed=passed,
        severity=item["severity"],
        message=message,
        auto_repairable=item["auto_repairable"],
        repair_plan=None,
    )


# ---------------------------------------------------------------------------
# 路由：POST /api/diagnostic/autofix/{check_id}
# ---------------------------------------------------------------------------
@router.post("/api/diagnostic/autofix/{check_id}", response_model=DiagnosticRunResult)
@with_db_retry
async def diagnostic_autofix(check_id: str) -> DiagnosticRunResult:
    """尝试自动修复诊断项（channels DoctorAdapter 调用）。

    仅 auto_repairable=true 的项可调，否则抛 INVALID_PARAMS。
    当前可修复项：
    - wechat_running：pkill -x wechat 杀进程 → 等 2s 检查 autostart 是否拉起
      → 未拉起则 bridge 显式启动（start_wechat）作为兜底
    - xdotool_available：不可修复，返回 passed=false + "请重建容器"
      （xdotool 缺失说明容器构建异常，bridge 内无法修复，只能重建）
    - db_accessible：加密 DB 时尝试自动提取 key（P1 能力），失败返回 repair_plan

    Args:
        check_id: 路径参数，诊断项 ID

    Returns:
        DiagnosticRunResult：含 passed / message（描述修复结果或失败原因）

    Raises:
        BridgeError(INVALID_PARAMS): 未知 check_id 或该项不可自动修复（HTTP 400）

    Notes:
        - wechat_running 修复流程：
          1. pkill 杀旧进程
          2. 等 2s 让 autostart watchdog 拉起
          3. 若 autostart 已拉起 → 返回 passed=true + 新 PID
          4. 若 autostart 未拉起 → bridge 显式启动（start_wechat）
          5. 显式启动成功 → passed=true + 新 PID
          6. 显式启动失败 → passed=false + repair_plan 提示
        - 调用方收到 passed=true 后应间隔 3-5s 调 /api/diagnostic/run/wechat_running
          确认实际状态（进程可能在初始化中，窗口尚未出现）
    """
    item = _DIAGNOSTIC_ITEM_BY_ID.get(check_id)
    if item is None:
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"未知诊断项: {check_id}",
        )
    if not item["auto_repairable"]:
        raise BridgeError(
            code="INVALID_PARAMS",
            message="该项不可自动修复",
        )

    if check_id == "wechat_running":
        xdotool = _require_xdotool()

        # 1. pkill 旧进程（不强制 SIGKILL，给微信优雅退出机会）
        try:
            proc = await asyncio.create_subprocess_exec(
                "pkill", "-x", "wechat",
                stdout=asyncio.subprocess.DEVNULL,
                stderr=asyncio.subprocess.DEVNULL,
            )
            await proc.wait()
        except Exception:
            pass

        # 2. 等 2s 让 autostart watchdog 拉起
        await asyncio.sleep(2)

        # 3. 检查 autostart 是否已拉起
        new_pid = await xdotool.check_wechat_pid()

        # 4. autostart 未拉起，bridge 显式启动兜底
        if new_pid is None:
            new_pid = await xdotool.start_wechat(timeout_sec=10)

        # 5. 返回结果
        if new_pid is not None:
            return DiagnosticRunResult(
                check_id=check_id,
                passed=True,
                severity=item["severity"],
                message=f"微信进程已恢复，PID={new_pid}",
                auto_repairable=True,
                repair_plan=None,
            )
        return DiagnosticRunResult(
            check_id=check_id,
            passed=False,
            severity=item["severity"],
            message="autostart 未拉起且 bridge 显式启动失败",
            auto_repairable=True,
            repair_plan="检查 X 会话是否正常（Xvnc/openbox），或通过 VNC 桌面手动启动微信",
        )
    elif check_id == "db_accessible":
        db_reader = _require_db_reader()
        # 检查 DB 状态
        status = await asyncio.to_thread(db_reader.check_db_status)
        if status == "ok":
            return DiagnosticRunResult(
                check_id=check_id,
                passed=True,
                severity=item["severity"],
                message="DB 可读，无需修复",
                auto_repairable=item["auto_repairable"],
                repair_plan=None,
            )
        if status in ("need_init", "key_invalid"):
            # 尝试自动提取 key（阻塞等待结果，复用公共加锁路径）
            extracted = await _auto_extract_with_lock()
            if extracted:
                return DiagnosticRunResult(
                    check_id=check_id,
                    passed=True,
                    severity=item["severity"],
                    message="自动提取 DB 密钥成功，DB 已可解密读取",
                    auto_repairable=item["auto_repairable"],
                    repair_plan=None,
                )
            else:
                return DiagnosticRunResult(
                    check_id=check_id,
                    passed=False,
                    severity=item["severity"],
                    message="自动提取 DB 密钥失败",
                    auto_repairable=item["auto_repairable"],
                    repair_plan="通过 POST /api/db/decrypt 手动注入 64 位 hex 密钥，或设置 WOC_DB_KEY 环境变量",
                )
        # not_found / unreadable 不可自动修复
        return DiagnosticRunResult(
            check_id=check_id,
            passed=False,
            severity=item["severity"],
            message=f"DB 状态: {status}，无法自动修复",
            auto_repairable=item["auto_repairable"],
            repair_plan="检查 /config/xwechat_files 下 DB 文件是否存在且权限正确",
        )
    elif check_id == "xdotool_available":
        return DiagnosticRunResult(
            check_id=check_id,
            passed=False,
            severity=item["severity"],
            message="xdotool 不可用，请重建容器",
            auto_repairable=True,
            repair_plan=None,
        )

    # 理论不可达
    raise BridgeError(
        code="INVALID_PARAMS",
        message=f"未知诊断项: {check_id}",
    )
