"""
运维控制面板 API

提供服务器各环境的服务状态查看、启停控制、Git 操作和配置管理。
面向管理后台的运维面板页面。
"""

import asyncio
import os
import subprocess
import platform
from datetime import datetime
from pathlib import Path
from typing import Any

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

from ..config import OPS_SERVER_BASE

router = APIRouter(prefix="/api/ops", tags=["运维面板"])

# ---- 环境定义 ----
# CHANGE 2026-03-04 | 从 config 读取，消除硬编码 D 盘路径
_SERVER_BASE = Path(OPS_SERVER_BASE)

ENVIRONMENTS: dict[str, dict[str, Any]] = {
    "test": {
        "label": "测试环境",
        "repo_path": str(_SERVER_BASE / "test" / "repo"),
        "branch": "test",
        "port": 8001,
        "bat_script": str(_SERVER_BASE / "scripts" / "start-test-api.bat"),
        "window_title": "NeoZQYY Test API",
    },
    "prod": {
        "label": "正式环境",
        "repo_path": str(_SERVER_BASE / "prod" / "repo"),
        "branch": "master",
        "port": 8000,
        "bat_script": str(_SERVER_BASE / "scripts" / "start-prod-api.bat"),
        "window_title": "NeoZQYY Prod API",
    },
}


# ---- 数据模型 ----

class ServiceStatus(BaseModel):
    env: str
    label: str
    running: bool
    pid: int | None = None
    port: int
    uptime_seconds: float | None = None
    memory_mb: float | None = None
    cpu_percent: float | None = None


class GitInfo(BaseModel):
    env: str
    branch: str
    last_commit_hash: str
    last_commit_message: str
    last_commit_time: str
    has_local_changes: bool


class SystemInfo(BaseModel):
    cpu_percent: float
    memory_total_gb: float
    memory_used_gb: float
    memory_percent: float
    disk_total_gb: float
    disk_used_gb: float
    disk_percent: float
    boot_time: str


class EnvFileContent(BaseModel):
    content: str


class GitPullResult(BaseModel):
    env: str
    success: bool
    output: str


class ServiceActionResult(BaseModel):
    env: str
    action: str
    success: bool
    message: str


# ---- 辅助函数 ----

def _find_uvicorn_process(port: int) -> psutil.Process | None:
    """查找监听指定端口的 uvicorn 进程。"""
    for proc in psutil.process_iter(["pid", "name", "cmdline"]):
        try:
            cmdline = proc.info.get("cmdline") or []
            cmdline_str = " ".join(cmdline)
            # 匹配 uvicorn 进程且包含对应端口
            if "uvicorn" in cmdline_str and str(port) in cmdline_str:
                return proc
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
    return None


def _run_cmd(cmd: str | list[str], cwd: str | None = None, timeout: int = 30) -> tuple[bool, str]:
    """执行命令并返回 (成功, 输出)。"""
    try:
        result = subprocess.run(
            cmd,
            cwd=cwd,
            capture_output=True,
            text=True,
            timeout=timeout,
            shell=isinstance(cmd, str),
            encoding="utf-8",
            errors="replace",
        )
        output = (result.stdout + "\n" + result.stderr).strip()
        return result.returncode == 0, output
    except subprocess.TimeoutExpired:
        return False, "命令执行超时"
    except Exception as e:
        return False, str(e)


# ---- 系统信息 ----

@router.get("/system", response_model=SystemInfo)
async def get_system_info():
    """获取服务器系统资源概况。"""
    mem = psutil.virtual_memory()
    disk = psutil.disk_usage("D:\\") if os.path.exists("D:\\") else psutil.disk_usage("/")
    boot = datetime.fromtimestamp(psutil.boot_time())
    return SystemInfo(
        cpu_percent=psutil.cpu_percent(interval=0.5),
        memory_total_gb=round(mem.total / (1024 ** 3), 2),
        memory_used_gb=round(mem.used / (1024 ** 3), 2),
        memory_percent=mem.percent,
        disk_total_gb=round(disk.total / (1024 ** 3), 2),
        disk_used_gb=round(disk.used / (1024 ** 3), 2),
        disk_percent=disk.percent,
        boot_time=boot.isoformat(),
    )


# ---- 服务状态 ----

@router.get("/services", response_model=list[ServiceStatus])
async def get_services_status():
    """获取所有环境的服务运行状态。"""
    results = []
    for env_key, env_cfg in ENVIRONMENTS.items():
        proc = _find_uvicorn_process(env_cfg["port"])
        if proc:
            try:
                mem_info = proc.memory_info()
                create_time = proc.create_time()
                results.append(ServiceStatus(
                    env=env_key,
                    label=env_cfg["label"],
                    running=True,
                    pid=proc.pid,
                    port=env_cfg["port"],
                    uptime_seconds=round(datetime.now().timestamp() - create_time, 1),
                    memory_mb=round(mem_info.rss / (1024 ** 2), 1),
                    cpu_percent=proc.cpu_percent(interval=0.1),
                ))
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                results.append(ServiceStatus(
                    env=env_key, label=env_cfg["label"],
                    running=False, port=env_cfg["port"],
                ))
        else:
            results.append(ServiceStatus(
                env=env_key, label=env_cfg["label"],
                running=False, port=env_cfg["port"],
            ))
    return results


# ---- 服务启停 ----

@router.post("/services/{env}/start", response_model=ServiceActionResult)
async def start_service(env: str):
    """启动指定环境的后端服务。"""
    if env not in ENVIRONMENTS:
        raise HTTPException(404, f"未知环境: {env}")

    cfg = ENVIRONMENTS[env]
    proc = _find_uvicorn_process(cfg["port"])
    if proc:
        return ServiceActionResult(
            env=env, action="start", success=True,
            message=f"服务已在运行中 (PID: {proc.pid})",
        )

    bat_path = cfg["bat_script"]
    if not os.path.exists(bat_path):
        raise HTTPException(400, f"启动脚本不存在: {bat_path}")

    # 通过 start 命令在新窗口中启动 bat 脚本
    try:
        subprocess.Popen(
            f'start "{cfg["window_title"]}" cmd /c "{bat_path}"',
            shell=True,
        )
        # 等待进程启动
        await asyncio.sleep(3)
        new_proc = _find_uvicorn_process(cfg["port"])
        if new_proc:
            return ServiceActionResult(
                env=env, action="start", success=True,
                message=f"服务已启动 (PID: {new_proc.pid})",
            )
        else:
            return ServiceActionResult(
                env=env, action="start", success=False,
                message="启动命令已执行，但未检测到进程，请检查日志",
            )
    except Exception as e:
        return ServiceActionResult(
            env=env, action="start", success=False, message=str(e),
        )


@router.post("/services/{env}/stop", response_model=ServiceActionResult)
async def stop_service(env: str):
    """停止指定环境的后端服务。"""
    if env not in ENVIRONMENTS:
        raise HTTPException(404, f"未知环境: {env}")

    cfg = ENVIRONMENTS[env]
    proc = _find_uvicorn_process(cfg["port"])
    if not proc:
        return ServiceActionResult(
            env=env, action="stop", success=True, message="服务未在运行",
        )

    try:
        # 终止进程树（包括子进程）
        parent = psutil.Process(proc.pid)
        children = parent.children(recursive=True)
        for child in children:
            child.terminate()
        parent.terminate()
        # 等待进程退出
        gone, alive = psutil.wait_procs([parent] + children, timeout=5)
        for p in alive:
            p.kill()
        return ServiceActionResult(
            env=env, action="stop", success=True, message="服务已停止",
        )
    except Exception as e:
        return ServiceActionResult(
            env=env, action="stop", success=False, message=str(e),
        )


@router.post("/services/{env}/restart", response_model=ServiceActionResult)
async def restart_service(env: str):
    """重启指定环境的后端服务。"""
    stop_result = await stop_service(env)
    if not stop_result.success and "未在运行" not in stop_result.message:
        return ServiceActionResult(
            env=env, action="restart", success=False,
            message=f"停止失败: {stop_result.message}",
        )
    await asyncio.sleep(1)
    start_result = await start_service(env)
    return ServiceActionResult(
        env=env, action="restart",
        success=start_result.success,
        message=start_result.message,
    )


# ---- Git 操作 ----

@router.get("/git", response_model=list[GitInfo])
async def get_git_info():
    """获取所有环境的 Git 状态。"""
    results = []
    for env_key, env_cfg in ENVIRONMENTS.items():
        repo = env_cfg["repo_path"]
        if not os.path.isdir(os.path.join(repo, ".git")):
            results.append(GitInfo(
                env=env_key, branch="N/A",
                last_commit_hash="N/A", last_commit_message="仓库不存在",
                last_commit_time="", has_local_changes=False,
            ))
            continue

        _, branch = _run_cmd(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=repo)
        _, log_out = _run_cmd(
            ["git", "log", "-1", "--format=%H|%s|%ci"],
            cwd=repo,
        )
        _, status_out = _run_cmd(["git", "status", "--porcelain"], cwd=repo)

        parts = log_out.strip().split("|", 2) if log_out else ["", "", ""]
        results.append(GitInfo(
            env=env_key,
            branch=branch.strip(),
            last_commit_hash=parts[0][:8] if parts[0] else "N/A",
            last_commit_message=parts[1] if len(parts) > 1 else "",
            last_commit_time=parts[2] if len(parts) > 2 else "",
            has_local_changes=bool(status_out.strip()),
        ))
    return results


@router.post("/git/{env}/pull", response_model=GitPullResult)
async def git_pull(env: str):
    """对指定环境执行 git pull。"""
    if env not in ENVIRONMENTS:
        raise HTTPException(404, f"未知环境: {env}")

    cfg = ENVIRONMENTS[env]
    repo = cfg["repo_path"]
    if not os.path.isdir(os.path.join(repo, ".git")):
        raise HTTPException(400, f"仓库路径不存在: {repo}")

    success, output = _run_cmd(["git", "pull", "--ff-only"], cwd=repo, timeout=60)
    return GitPullResult(env=env, success=success, output=output)


@router.post("/git/{env}/sync-deps", response_model=ServiceActionResult)
async def sync_deps(env: str):
    """对指定环境执行 uv sync --all-packages。"""
    if env not in ENVIRONMENTS:
        raise HTTPException(404, f"未知环境: {env}")

    cfg = ENVIRONMENTS[env]
    repo = cfg["repo_path"]
    success, output = _run_cmd(["uv", "sync", "--all-packages"], cwd=repo, timeout=120)
    return ServiceActionResult(
        env=env, action="sync-deps", success=success, message=output[:500],
    )


# ---- 环境配置管理 ----

@router.get("/env-file/{env}")
async def get_env_file(env: str):
    """读取指定环境的 .env 文件（敏感值脱敏）。"""
    if env not in ENVIRONMENTS:
        raise HTTPException(404, f"未知环境: {env}")

    env_path = Path(ENVIRONMENTS[env]["repo_path"]) / ".env"
    if not env_path.exists():
        raise HTTPException(404, f".env 文件不存在: {env_path}")

    lines = env_path.read_text(encoding="utf-8").splitlines()
    masked_lines = []
    sensitive_keys = {"PASSWORD", "SECRET", "TOKEN", "DSN", "APP_SECRET"}
    for line in lines:
        stripped = line.strip()
        if stripped and not stripped.startswith("#") and "=" in stripped:
            key = stripped.split("=", 1)[0].strip()
            if any(s in key.upper() for s in sensitive_keys):
                masked_lines.append(f"{key}=********")
                continue
        masked_lines.append(line)
    return {"env": env, "content": "\n".join(masked_lines)}
