#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
一键启动 Web 控制台：
  生产：构建 web-console（若需要）+ 仅 uvicorn（单端口托管 API + 静态页）
  开发：uvicorn + Next dev（默认绑定 0.0.0.0，局域网可访问）

自动加载 config/runtime.env（若存在，不覆盖已有环境变量）。

用法:
  python scripts/launch.py
  python scripts/launch.py --dev
  python scripts/launch.py --port 8101
  python scripts/launch.py --skip-build
  python scripts/launch.py --no-env-check

环境变量: HOST, PORT, NEXT_DEV_PORT, WEB_STACK=api, API_PROXY_TARGET（开发）
"""
from __future__ import annotations

import argparse
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path

if hasattr(sys.stdout, "reconfigure"):
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

ROOT = Path(__file__).resolve().parent.parent
SCRIPTS_DIR = Path(__file__).resolve().parent
CONSOLE = ROOT / "web-console"
OUT_INDEX = CONSOLE / "out" / "index.html"

sys.path.insert(0, str(SCRIPTS_DIR))
import env_check  # noqa: E402


def load_runtime_env() -> None:
    path = ROOT / "config" / "runtime.env"
    if not path.is_file():
        return
    for raw in path.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            continue
        k, _, v = line.partition("=")
        k, v = k.strip(), v.strip().strip('"').strip("'")
        if k and k not in os.environ:
            os.environ[k] = v


def _which_npm() -> str | None:
    return shutil.which("npm") or shutil.which("npm.cmd")


def _npm(args: list[str], *, cwd: Path, env: dict | None = None) -> int:
    full_env = {**os.environ, **(env or {})}
    if sys.platform == "win32":
        return subprocess.call(
            "npm " + " ".join(args),
            cwd=cwd,
            shell=True,
            env=full_env,
        )
    npm = _which_npm()
    if not npm:
        return 127
    return subprocess.call([npm, *args], cwd=cwd, env=full_env)


def ensure_console_built(*, skip: bool) -> bool:
    """返回是否已存在可托管的静态 index。"""
    if skip:
        return OUT_INDEX.is_file()
    if not CONSOLE.is_dir():
        print("[launch] 未找到 web-console，将仅使用根目录 index.html（若存在）。")
        return False
    if not _which_npm():
        print("[launch] 未检测到 npm，跳过前端构建；请先安装 Node.js 或手动在 web-console 执行 npm run build。")
        return OUT_INDEX.is_file()
    if OUT_INDEX.is_file():
        return True
    print("[launch] 正在安装依赖并构建 Web 控制台（首次略慢）…")
    lock = CONSOLE / "package-lock.json"
    if lock.is_file():
        if _npm(["ci"], cwd=CONSOLE) != 0:
            print("[launch] npm ci 失败，尝试 npm install…")
            if _npm(["install"], cwd=CONSOLE) != 0:
                sys.exit(1)
    else:
        if _npm(["install"], cwd=CONSOLE) != 0:
            sys.exit(1)
    if _npm(["run", "build"], cwd=CONSOLE) != 0:
        sys.exit(1)
    print("[launch] 前端构建完成。")
    return True


def _terminate_process(p: subprocess.Popen | None) -> None:
    if p is None or p.poll() is not None:
        return
    p.terminate()
    try:
        p.wait(timeout=8)
    except subprocess.TimeoutExpired:
        p.kill()
        p.wait(timeout=3)


def run_dev(host: str, port: str, next_port: str) -> None:
    if not _which_npm():
        print("[launch] 开发模式需要 Node.js/npm。仅起 API 请用: python scripts/launch.py --api-only --reload")
        sys.exit(1)
    api_url = f"http://127.0.0.1:{port}"
    env = os.environ.copy()
    env.setdefault("API_PROXY_TARGET", api_url)

    api_cmd = [
        sys.executable,
        "-m",
        "uvicorn",
        "web:app",
        "--host",
        host,
        "--port",
        port,
        "--reload",
    ]
    os.chdir(ROOT)
    api: subprocess.Popen | None = None
    ui: subprocess.Popen | None = None
    next_args = f"npm run dev -- --hostname 0.0.0.0 --port {next_port}"
    try:
        api = subprocess.Popen(api_cmd, cwd=ROOT)
        time.sleep(0.6)
        if api.poll() is not None:
            print("[launch] API 进程异常退出，请检查端口是否被占用。")
            sys.exit(api.returncode or 1)
        if sys.platform == "win32":
            ui = subprocess.Popen(next_args, cwd=CONSOLE, shell=True, env=env)
        else:
            ui = subprocess.Popen(
                ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", next_port],
                cwd=CONSOLE,
                env=env,
            )
        print()
        print("  —— 开发模式（局域网可访问）——")
        print(f"  控制台界面:  http://0.0.0.0:{next_port}  或用本机 IP + 端口")
        print(f"  API:         http://{host}:{port}/  （Next 已代理同名接口到 {api_url}）")
        print("  按 Ctrl+C 可同时结束前后端")
        if host == "0.0.0.0":
            print("  提示: 绑定 0.0.0.0 时请勿把管理端口暴露到公网")
        print()
        while True:
            time.sleep(0.5)
            if api.poll() is not None:
                print("[launch] API 已退出")
                break
            if ui and ui.poll() is not None:
                print("[launch] Next 已退出")
                break
    except KeyboardInterrupt:
        print("\n[launch] 正在关闭…")
    finally:
        _terminate_process(ui)
        _terminate_process(api)


def run_api_only(host: str, port: str, reload: bool) -> None:
    os.chdir(ROOT)
    cmd = [sys.executable, "-m", "uvicorn", "web:app", "--host", host, "--port", port]
    if reload:
        cmd.append("--reload")
    os.execvp(sys.executable, cmd)


def run_prod(host: str, port: str, skip_build: bool, reload: bool) -> None:
    ensure_console_built(skip=skip_build)
    run_api_only(host, port, reload)


def main() -> None:
    load_runtime_env()

    default_port = os.environ.get("PORT", "8001")
    ap = argparse.ArgumentParser(description="直播控制台一键启动")
    ap.add_argument("--dev", action="store_true", help="开发：API + Next（需 Node）")
    ap.add_argument(
        "--api-only",
        action="store_true",
        help="仅 uvicorn，不构建、不启动 Next",
    )
    ap.add_argument("--host", default=None, help="监听地址（默认生产 0.0.0.0，可经 HOST 环境变量）")
    ap.add_argument("--port", default=None, help="端口（默认 PORT 或 8001）")
    ap.add_argument("--skip-build", action="store_true", help="跳过前端构建检测")
    ap.add_argument("--reload", action="store_true", help="uvicorn --reload（生产慎用）")
    ap.add_argument("--no-env-check", action="store_true", help="跳过环境分析")
    args = ap.parse_args()

    if os.environ.get("WEB_STACK", "").lower() == "api":
        args.api_only = True

    port = str(args.port if args.port is not None else default_port)
    next_port = os.environ.get("NEXT_DEV_PORT", "3000")

    if args.dev:
        host = args.host or os.environ.get("HOST") or "0.0.0.0"
    else:
        host = args.host or os.environ.get("HOST") or "0.0.0.0"

    need_npm = args.dev or (
        not args.skip_build and not args.api_only and not OUT_INDEX.is_file()
    )
    if not args.no_env_check:
        env_check.print_env_report(
            dev=bool(args.dev),
            need_npm=need_npm,
            host=host,
            port=port,
            next_port=next_port,
        )

    if args.dev:
        run_dev(host, port, next_port)
        return

    if args.api_only:
        run_api_only(host, port, args.reload)
        return

    run_prod(host, port, skip_build=args.skip_build, reload=args.reload)


if __name__ == "__main__":
    main()
