#!/usr/bin/env python3
"""E2E smoke for insurance-sales-pipeline on shared insurance DB."""
from __future__ import annotations

import json
import os
import sqlite3
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path

HERE = Path(__file__).resolve().parent


def _discover_skills_root() -> Path:
    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
    candidates = [HERE.parents[2], HERE.parents[1], Path("/app/skills"), Path.cwd() / "skills"]
    for c in candidates:
        try:
            if (c / "insurance-customer-policy").is_dir():
                return c.resolve()
        except OSError:
            continue
    raise SystemExit("[FAIL] cannot discover skills root for smoke test")


def _resolve_customer_pkg(skills_root: Path) -> Path:
    candidates = [
        skills_root / "insurance-customer-policy" / "scripts",
        skills_root / "insurance-customer-policy" / "src" / "scripts",
        skills_root / "insurance-customer-policy" / "src" / "src",
    ]
    for p in candidates:
        try:
            if (p / "insurance_customer_cli" / "__init__.py").is_file():
                return p
        except OSError:
            continue
    raise SystemExit("[FAIL] cannot locate insurance_customer_cli package for smoke test")


SKILLS_ROOT = _discover_skills_root()
CUSTOMER_PKG = _resolve_customer_pkg(SKILLS_ROOT)


def _resolve_cli(skill_folder: str) -> Path:
    candidates = [
        SKILLS_ROOT / skill_folder / "scripts" / "cli.py",
        SKILLS_ROOT / skill_folder / "src" / "scripts" / "cli.py",
    ]
    for p in candidates:
        if p.is_file():
            return p
    raise SystemExit(f"[FAIL] cannot locate cli.py for {skill_folder}")


PIPELINE_CLI = _resolve_cli("insurance-sales-pipeline")
CUSTOMER_CLI = _resolve_cli("insurance-customer-policy")


def _run_json(cmd: list[str], env: dict[str, str], label: str, timeout: int = 120) -> dict:
    r = subprocess.run(cmd, env=env, capture_output=True, text=True, encoding="utf-8", timeout=timeout)
    if r.returncode not in (0, 1):
        raise SystemExit(f"{label} rc={r.returncode}\nCMD: {' '.join(cmd)}\nSTDOUT:\n{r.stdout}\nSTDERR:\n{r.stderr}")
    out = (r.stdout or "").strip()
    if not out:
        raise SystemExit(f"{label} empty stdout\nCMD: {' '.join(cmd)}\nrc={r.returncode}\nSTDERR:\n{r.stderr}")
    try:
        return json.loads(out)
    except json.JSONDecodeError as e:
        raise SystemExit(f"{label} bad json: {e}\nCMD: {' '.join(cmd)}\nSTDOUT:\n{out}\nSTDERR:\n{r.stderr}")


def run_pipeline(*args: str) -> dict:
    cmd = [sys.executable, str(PIPELINE_CLI), *args]
    env = {**os.environ, "PYTHONIOENCODING": "utf-8"}
    return _run_json(cmd, env, "pipeline", timeout=120)


def run_customer(*args: str) -> dict:
    cmd = [sys.executable, str(CUSTOMER_CLI), *args]
    env = {**os.environ, "PYTHONIOENCODING": "utf-8"}
    return _run_json(cmd, env, "customer", timeout=60)


def assert_ok(d: dict, *args: str) -> dict:
    if not d.get("ok"):
        raise SystemExit(f"expected ok=True got {d} args={args}")
    return d


def assert_err(d: dict, code: str) -> None:
    if d.get("ok") or d.get("error") != code:
        raise SystemExit(f"expected error={code} got {d}")


def main() -> int:
    tmp = tempfile.mkdtemp(prefix="ins_pipe_smoke_")
    shared = str(Path(tmp) / "insurance.sqlite3")
    os.environ["INSURANCE_DB_PATH"] = shared
    os.environ["INSURANCE_PIPELINE_DB_PATH"] = shared
    os.environ["INSURANCE_CUSTOMER_DB_PATH"] = shared
    month = datetime.now().strftime("%Y-%m")

    print("[setup] customers")
    assert_ok(run_customer("customer-add", "--name", "张先生", "--phone", "13800138000",
                           "--age", "35", "--income", "300000"))
    assert_ok(run_customer("customer-add", "--name", "李女士", "--phone", "13900139000", "--age", "40"))
    assert_ok(run_customer("customer-add", "--name", "王先生", "--phone", "13700137000", "--age", "50"))

    print("[1] deal-add: missing customer")
    assert_err(run_pipeline("deal-add", "--customer-id", "C999"), "customer_not_found")

    print("[2] deal-add x3 (different stages will be assigned later)")
    d = assert_ok(run_pipeline("deal-add", "--customer-id", "C001",
                               "--product-type", "重疾险",
                               "--stage", "在谈", "--estimated-premium", "8000"))
    assert d["id"] == "D001" and d["stage"] == "在谈"
    assert_ok(run_pipeline("deal-add", "--customer-id", "C002",
                           "--product-type", "医疗险",
                           "--stage", "在谈", "--estimated-premium", "5000"))
    assert_ok(run_pipeline("deal-add", "--customer-id", "C003",
                           "--product-type", "重疾险",
                           "--stage", "在谈", "--estimated-premium", "12000"))

    print("[3] deal-stage: bad stage rejected")
    assert_err(run_pipeline("deal-stage", "--deal-id", "D001", "--to", "瞎写阶段"), "bad_stage")

    print("[4] deal-stage progression D001: 在谈 -> 方案中 -> 议价 -> 已成交")
    assert_ok(run_pipeline("deal-stage", "--deal-id", "D001", "--to", "方案中", "--note", "已出方案"))
    assert_ok(run_pipeline("deal-stage", "--deal-id", "D001", "--to", "议价"))
    assert_ok(run_pipeline("deal-stage", "--deal-id", "D001", "--to", "已成交",
                           "--actual-premium", "8500"))

    print("[5] deal-stage D002 to 方案中")
    assert_ok(run_pipeline("deal-stage", "--deal-id", "D002", "--to", "方案中"))

    print("[6] deal-query --status active (should be D002 in 方案中, D003 in 在谈)")
    d = assert_ok(run_pipeline("deal-query", "--status", "active"))
    stages = sorted([r["stage"] for r in d["deals"]])
    assert stages == ["在谈", "方案中"], stages

    print("[7] deal-forecast")
    d = assert_ok(run_pipeline("deal-forecast", "--month", month))
    # D003 在谈 12000*0.2=2400; D002 方案中 5000*0.5=2500
    assert d["total_forecast"] == 2400 + 2500, d
    assert d["closed"] == 8500, d
    assert "重疾险" in d["by_type"]
    assert d["by_type"]["重疾险"] == 2400.0

    print("[8] target-track: set + read")
    assert_ok(run_pipeline("target-track", "--month", month, "--target", "30000"))
    d = assert_ok(run_pipeline("target-track", "--month", month))
    assert d["target"] == 30000
    assert d["closed"] == 8500
    assert "目标跟踪" in d["markdown"]
    assert d["forecast_breakdown"]["在谈"] == 2400.0
    assert d["forecast_breakdown"]["方案中"] == 2500.0

    print("[9] deal-lost: bad reason rejected")
    assert_err(run_pipeline("deal-lost", "--deal-id", "D003", "--reason", "瞎写"), "bad_reason")

    print("[10] deal-lost D003 with reason=价格")
    assert_ok(run_pipeline("deal-lost", "--deal-id", "D003",
                           "--reason", "价格", "--note", "客户选了B公司"))
    d = assert_ok(run_pipeline("deal-query", "--deal-id", "D003"))
    assert d["deals"][0]["stage"] == "已流失"
    assert d["deals"][0]["lost_reason"] == "价格"

    print("[11] deal-lost --stats")
    d = assert_ok(run_pipeline("deal-lost", "--stats", "--month", month))
    assert d["total_count"] == 1
    assert d["by_reason"][0]["reason"] == "价格"

    print("[12] deal-update")
    assert_ok(run_pipeline("deal-update", "--deal-id", "D002",
                           "--set", '{"estimated_premium": 6000, "note": "客户加保"}'))
    d = assert_ok(run_pipeline("deal-query", "--deal-id", "D002"))
    assert d["deals"][0]["estimated_premium"] == 6000

    print("[13] deal-stage_log整链")
    conn = sqlite3.connect(os.environ["INSURANCE_DB_PATH"])
    rows = list(conn.execute("SELECT to_stage FROM deal_stage_log WHERE deal_id='D001' ORDER BY id"))
    conn.close()
    assert [r[0] for r in rows] == ["在谈", "方案中", "议价", "已成交"], [r[0] for r in rows]

    print("[14] dashboard")
    d = assert_ok(run_pipeline("dashboard", "--json"))
    assert d["this_month_closed"] == 8500
    assert d["this_month_target"] == 30000
    assert d["active_deals"] >= 1

    print("[15] deal-delete")
    assert_err(run_pipeline("deal-delete", "--deal-id", "D002"), "need_confirm")
    assert_ok(run_pipeline("deal-delete", "--deal-id", "D002", "--yes"))

    print("ALL OK")
    return 0


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