#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""保险系列云端超级回归测试（失败不中断版）。

目标：
1) 尽可能覆盖所有保险 skill 功能；
2) 任一子测试失败不会阻塞后续测试；
3) 最终输出统一汇总报告与日志目录，方便一次定位多处问题。

推荐在云端执行：
    python3 /app/skills/insurance-shared-data/src/scripts/cloud_insurance_super_test.py
或（扁平安装）：
    python3 /app/skills/insurance-shared-data/scripts/cloud_insurance_super_test.py

可选参数：
    --skills-root /app/skills
    --artifacts-dir /tmp/insurance-super-test
    --skip-full
    --always-zero
"""
from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any


@dataclass
class StepResult:
    name: str
    ok: bool
    returncode: int
    seconds: float
    stdout_file: str
    stderr_file: str
    error: str = ""


def _discover_skills_root(explicit: str | None) -> Path:
    if explicit:
        p = Path(explicit).expanduser().resolve()
        if p.is_dir():
            return p
        raise SystemExit(f"[FAIL] --skills-root 不存在: {p}")

    env = os.environ.get("INSURANCE_SKILLS_ROOT") or os.environ.get("SKILLS_ROOT")
    if env:
        p = Path(env).expanduser().resolve()
        if p.is_dir():
            return p
        raise SystemExit(f"[FAIL] INSURANCE_SKILLS_ROOT/SKILLS_ROOT 不存在: {p}")

    here = Path(__file__).resolve()
    for c in [here.parents[3], Path("/app/skills"), Path.cwd() / "skills", Path.cwd()]:
        try:
            if (c / "insurance-customer-policy").is_dir():
                return c.resolve()
        except OSError:
            continue
    raise SystemExit("[FAIL] 无法定位 skills 根目录，请显式传入 --skills-root 或设置 INSURANCE_SKILLS_ROOT")


def _must_file(p: Path) -> None:
    if not p.is_file():
        raise SystemExit(f"[FAIL] 文件不存在: {p}")


def _resolve_script(skills_root: Path, skill: str, script_name: str) -> Path:
    """Resolve script path in both source and flattened layouts."""
    candidates = [
        skills_root / skill / "src" / "scripts" / script_name,
        skills_root / skill / "scripts" / script_name,
        Path("/app/skills") / skill / "src" / "scripts" / script_name,
        Path("/app/skills") / skill / "scripts" / script_name,
    ]
    for p in candidates:
        try:
            if p.is_file():
                return p
        except OSError:
            continue
    tried = "\n  - ".join(str(x) for x in candidates)
    raise SystemExit(f"[FAIL] 找不到脚本 {skill}/{script_name}，已尝试:\n  - {tried}")


def _write_text(path: Path, txt: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(txt, encoding="utf-8")


def _run_step(
    name: str,
    cmd: list[str],
    *,
    env: dict[str, str],
    cwd: Path,
    logs_dir: Path,
    timeout: int = 1800,
) -> StepResult:
    safe = "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in name).strip("_")
    out_file = logs_dir / f"{safe}.stdout.log"
    err_file = logs_dir / f"{safe}.stderr.log"
    t0 = time.time()
    try:
        r = subprocess.run(
            cmd,
            env=env,
            cwd=str(cwd),
            capture_output=True,
            text=True,
            encoding="utf-8",
            timeout=timeout,
        )
        _write_text(out_file, r.stdout or "")
        _write_text(err_file, r.stderr or "")
        return StepResult(
            name=name,
            ok=(r.returncode == 0),
            returncode=r.returncode,
            seconds=round(time.time() - t0, 2),
            stdout_file=str(out_file),
            stderr_file=str(err_file),
        )
    except Exception as e:  # noqa: BLE001
        _write_text(out_file, "")
        _write_text(err_file, str(e))
        return StepResult(
            name=name,
            ok=False,
            returncode=999,
            seconds=round(time.time() - t0, 2),
            stdout_file=str(out_file),
            stderr_file=str(err_file),
            error=str(e),
        )


def main() -> int:
    parser = argparse.ArgumentParser(description="保险 skill 云端超级回归（失败不中断）")
    parser.add_argument("--skills-root", help="skills 目录路径（包含 insurance-* 子目录）")
    parser.add_argument("--artifacts-dir", help="日志与报告输出目录")
    parser.add_argument("--skip-full", action="store_true", help="跳过 cloud_insurance_full_test.py 全量集成阶段")
    parser.add_argument("--always-zero", action="store_true", help="即使失败也返回 0（适合人工巡检场景）")
    args = parser.parse_args()

    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except (AttributeError, OSError):
        pass

    skills_root = _discover_skills_root(args.skills_root)
    scripts = {
        "customer_smoke": _resolve_script(skills_root, "insurance-customer-policy", "run_e2e_smoke.py"),
        "product_smoke": _resolve_script(skills_root, "insurance-product-analysis", "run_e2e_smoke.py"),
        "pipeline_smoke": _resolve_script(skills_root, "insurance-sales-pipeline", "run_e2e_smoke.py"),
        "schedule_smoke": _resolve_script(skills_root, "insurance-schedule-renewal", "run_e2e_smoke.py"),
        "full": _resolve_script(skills_root, "insurance-customer-policy", "cloud_insurance_full_test.py"),
        "dashboard_all": _resolve_script(skills_root, "insurance-customer-policy", "dashboard_all.py"),
        "super_self": _resolve_script(skills_root, "insurance-shared-data", "cloud_insurance_super_test.py"),
    }

    ts = time.strftime("%Y%m%d-%H%M%S", time.localtime())
    artifacts = Path(args.artifacts_dir).expanduser().resolve() if args.artifacts_dir else Path(f"/tmp/insurance-super-test-{ts}")
    logs_dir = artifacts / "logs"
    artifacts.mkdir(parents=True, exist_ok=True)
    logs_dir.mkdir(parents=True, exist_ok=True)

    # 每个子脚本自己会再创建隔离数据库，统一给定父目录便于回溯。
    env = dict(os.environ)
    env["INSURANCE_SKILLS_ROOT"] = str(skills_root)
    env["INSURANCE_CLOUD_TEST_DIR"] = str(artifacts / "db")
    env["PYTHONIOENCODING"] = "utf-8"

    steps: list[tuple[str, list[str], int]] = [
        ("customer_smoke", [sys.executable, str(scripts["customer_smoke"])], 1200),
        ("product_smoke", [sys.executable, str(scripts["product_smoke"])], 1200),
        ("pipeline_smoke", [sys.executable, str(scripts["pipeline_smoke"])], 1200),
        ("schedule_smoke", [sys.executable, str(scripts["schedule_smoke"])], 1200),
        ("dashboard_all_json", [sys.executable, str(scripts["dashboard_all"]), "--json"], 300),
        ("dashboard_all_md", [sys.executable, str(scripts["dashboard_all"]), "--md"], 300),
    ]
    if not args.skip_full:
        steps.extend(
            [
                ("cloud_full_smoke", [sys.executable, str(scripts["full"]), "--smoke"], 1800),
                ("cloud_full_all", [sys.executable, str(scripts["full"]), "-v"], 3600),
            ]
        )

    print("====== 保险技能超级回归（失败不中断）======")
    print(f"skills_root   : {skills_root}")
    print(f"artifacts_dir : {artifacts}")
    print(f"steps         : {len(steps)}")
    print("------------------------------------------")

    results: list[StepResult] = []
    for idx, (name, cmd, timeout) in enumerate(steps, start=1):
        print(f"[{idx}/{len(steps)}] RUN {name}")
        r = _run_step(name, cmd, env=env, cwd=skills_root, logs_dir=logs_dir, timeout=timeout)
        results.append(r)
        if r.ok:
            print(f"  -> PASS ({r.seconds}s)")
        else:
            print(f"  -> FAIL rc={r.returncode} ({r.seconds}s)")

    passed = sum(1 for r in results if r.ok)
    failed = len(results) - passed
    summary: dict[str, Any] = {
        "ok": failed == 0,
        "skills_root": str(skills_root),
        "artifacts_dir": str(artifacts),
        "stats": {"total": len(results), "passed": passed, "failed": failed},
        "results": [r.__dict__ for r in results],
    }
    summary_file = artifacts / "summary.json"
    _write_text(summary_file, json.dumps(summary, ensure_ascii=False, indent=2))

    print("\n============== 汇总 ==============")
    print(f"PASS: {passed}")
    print(f"FAIL: {failed}")
    print(f"SUMMARY: {summary_file}")
    print(f"LOG DIR: {logs_dir}")

    if failed > 0:
        print("\n失败项：")
        for r in results:
            if not r.ok:
                print(f"- {r.name}: rc={r.returncode}")
                print(f"  stderr: {r.stderr_file}")
                print(f"  stdout: {r.stdout_file}")

    if failed > 0 and not args.always_zero:
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

