"""
企微智能IT支持服务台 — 部署包生成脚本（Windows 兼容版）
=======================================================
功能：
  1. 构建前端（H5 + 坐席端）
  2. 将前端产物 + 后端源码 + 配置文件打包为 zip
  3. 输出 zip 文件，可上传到服务器直接解压部署

用法：
  cd 项目根目录
  python deploy-server/package.py

说明：
  - 使用 Python 而非 bash，确保 Windows 上可直接运行
  - 打包前会自动执行 npm run build 构建前端
  - 如不想重新构建，传 --skip-build 参数跳过
"""

import os
import sys
import shutil
import subprocess
import zipfile
from pathlib import Path

# ─── 配置 ───────────────────────────────────────────────────────
PROJECT_ROOT = Path(__file__).resolve().parent.parent  # 项目根目录
SCRIPT_DIR = Path(__file__).resolve().parent            # deploy-server 目录
PACKAGE_NAME = "it-smart-desk-server-deploy"
ZIP_FILENAME = f"{PACKAGE_NAME}.zip"

# 需要打包的目录和文件映射
# key: 源路径（相对于 PROJECT_ROOT），value: 目标路径（在 zip 中的相对路径，含顶层目录）
PACKAGE_PREFIX = PACKAGE_NAME  # zip 内顶层目录名
INCLUDE_MAP = {
    "deploy-server/docker-compose.yml": f"{PACKAGE_PREFIX}/docker-compose.yml",
    "deploy-server/.env.example":       f"{PACKAGE_PREFIX}/.env.example",
    "deploy-server/deploy.sh":           f"{PACKAGE_PREFIX}/deploy.sh",
    "deploy-server/README.md":           f"{PACKAGE_PREFIX}/README.md",
    "deploy-server/nginx/nginx.conf":    f"{PACKAGE_PREFIX}/nginx/nginx.conf",
    "frontend-h5/dist":                  f"{PACKAGE_PREFIX}/frontend-h5/dist",
    "frontend-agent/dist":               f"{PACKAGE_PREFIX}/frontend-agent/dist",
    "backend":                           f"{PACKAGE_PREFIX}/backend",
}

# 后端目录中需要排除的文件/文件夹
BACKEND_EXCLUDE = {
    "__pycache__", ".pytest_cache", ".venv", "venv",
    "*.pyc", ".git", ".env", "*.egg-info",
}

# ─── 辅助函数 ───────────────────────────────────────────────────

def run_cmd(cmd: str, cwd: Path | None = None) -> bool:
    """执行命令，返回是否成功"""
    print(f"  执行: {cmd}")
    result = subprocess.run(
        cmd, shell=True, cwd=cwd,
        capture_output=True,
        encoding='utf-8', errors='replace'  # Windows 下 npm 输出 UTF-8，忽略解码错误
    )
    if result.returncode != 0:
        stderr = result.stderr.strip() if result.stderr else ""
        # npm build 偶尔有非致命警告，只在实际失败时报错
        if "error" in stderr.lower() or result.returncode != 0:
            print(f"  ⚠ 命令返回码: {result.returncode}")
            if stderr:
                # 只打印最后几行，避免刷屏
                lines = stderr.strip().split('\n')
                for line in lines[-3:]:
                    print(f"    {line}")
        return result.returncode == 0
    return True


def should_exclude(path: Path) -> bool:
    """判断文件/目录是否应排除"""
    name = path.name
    if name in {"__pycache__", ".pytest_cache", ".venv", "venv", ".git", ".env", "node_modules"}:
        return True
    if name.endswith(".pyc") or name.endswith(".egg-info"):
        return True
    return False


def add_dir_to_zip(zipf: zipfile.ZipFile, src_dir: Path, arc_prefix: str):
    """递归添加目录到 zip"""
    for item in src_dir.rglob("*"):
        if should_exclude(item):
            continue
        if item.is_file():
            # 计算在 zip 中的相对路径
            rel = item.relative_to(src_dir)
            arc = f"{arc_prefix}/{rel}".replace("\\", "/")
            zipf.write(item, arc)


# ─── 主流程 ─────────────────────────────────────────────────────

def build_frontends():
    """构建前端"""
    print("\n--- [1/2] 构建前端 ---")

    # H5 员工端
    h5_dir = PROJECT_ROOT / "frontend-h5"
    print("构建 H5 员工端...")
    if not run_cmd("npm install --quiet", cwd=h5_dir):
        print("  ⚠ npm install 失败，尝试继续...")
    if not run_cmd("npm run build", cwd=h5_dir):
        print("  ❌ H5 构建失败！")
        sys.exit(1)
    print("  ✅ H5 员工端构建完成")

    # 坐席工作台
    agent_dir = PROJECT_ROOT / "frontend-agent"
    print("构建坐席工作台...")
    if not run_cmd("npm install --quiet", cwd=agent_dir):
        print("  ⚠ npm install 失败，尝试继续...")
    if not run_cmd("npm run build", cwd=agent_dir):
        print("  ❌ 坐席端构建失败！")
        sys.exit(1)
    print("  ✅ 坐席工作台构建完成")


def create_package():
    """创建部署包 zip"""
    print("\n--- [2/2] 创建部署包 ---")

    zip_path = PROJECT_ROOT / ZIP_FILENAME

    # 检查必要文件是否存在
    checks = [
        (PROJECT_ROOT / "frontend-h5" / "dist" / "index.html", "H5 前端产物"),
        (PROJECT_ROOT / "frontend-agent" / "dist" / "index.html", "坐席端前端产物"),
        (PROJECT_ROOT / "backend" / "Dockerfile", "后端 Dockerfile"),
        (SCRIPT_DIR / "docker-compose.yml", "docker-compose.yml"),
        (SCRIPT_DIR / "nginx" / "nginx.conf", "nginx.conf"),
    ]
    for path, desc in checks:
        if not path.exists():
            print(f"  ❌ 缺少: {desc} ({path})")
            sys.exit(1)

    print(f"  正在打包到 {zip_path}...")

    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
        for src_rel, dst_rel in INCLUDE_MAP.items():
            src = PROJECT_ROOT / src_rel
            if not src.exists():
                print(f"  ⚠ 跳过不存在的路径: {src_rel}")
                continue

            if src.is_file():
                zf.write(src, dst_rel)
            elif src.is_dir():
                add_dir_to_zip(zf, src, dst_rel)

    # 显示结果
    size_mb = zip_path.stat().st_size / (1024 * 1024)
    print(f"  ✅ 部署包已生成: {zip_path}")
    print(f"     大小: {size_mb:.1f} MB")


def main():
    print("=" * 50)
    print("  智能IT支持服务台 — 部署包生成")
    print("=" * 50)

    # 检查是否跳过构建
    skip_build = "--skip-build" in sys.argv

    if skip_build:
        print("\n  ⏭ 跳过前端构建（使用现有 dist 目录）")
    else:
        build_frontends()

    create_package()

    # 输出后续步骤
    print("\n" + "=" * 50)
    print("  后续步骤：")
    print("=" * 50)
    print(f"""
  1. 上传部署包到服务器（通过堡垒机）：
     scp -o "ProxyJump=sxn@10.212.189.210:2222" \\
         {ZIP_FILENAME} \\
         sxn@10.80.0.136:/tmp/

  2. SSH 登录服务器（通过堡垒机）：
     ssh -J sxn@10.212.189.210:2222 sxn@10.80.0.136

  3. 在服务器上执行：
     sudo cp /tmp/{ZIP_FILENAME} /opt/
     cd /opt
     unzip {ZIP_FILENAME}
     mv {PACKAGE_NAME} wecom-it-desk
     cd wecom-it-desk
     cp .env.example .env
     vi .env                    # 编辑配置（阶段一 Mock 模式默认即可）
     chmod +x deploy.sh
     ./deploy.sh

  4. 配置 DNS（联系 IT 运维）：
     itsupport.servyou.com.cn → 10.80.0.136

  5. 浏览器验证：
     http://itsupport.servyou.com.cn/itdesk/
     http://itsupport.servyou.com.cn/itagent/
    """)


if __name__ == "__main__":
    main()
