#!/usr/bin/env python3
"""Self-test for verify_space.py. Run: python test_verify_space.py"""

import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

SCRIPT = Path(__file__).with_name("verify_space.py")
failures = []

HYP = "# 假设\n中位数 RMSE ≤ 0.9 × 均值 RMSE（10% 离群点）\n"
PLAN_OPEN = "# 方案\n基线：样本均值\n- [ ] G1 对等\n- [x] G2 种子\n"
PLAN_DONE = "# 方案\n基线：样本均值\n- [x] G1 对等\n- [x] G2 种子\n- [-] G3 不适用：无代理指标\n"
TASKS_RUN = "# 任务\n- [x] T001 [SU] 建目录：_verify-pilot/s/\n- [ ] T002 [BL] 跑基线：_verify-pilot/s/bl.log\n- [ ] T003 [AN] 汇总：RESULT.md\n"
TASKS_AN = "# 任务\n- [x] T001 [SU] 建目录\n- [x] T002 [BL] 跑基线\n- [ ] T003 [AN] 汇总\n"
TASKS_DONE = "# 任务\n- [x] T001 [SU] 建目录\n- [x] T002 [BL] 跑基线\n- [x] T003 [P] [TR] 跑处理组\n- [x] T004 [AN] 汇总\n"
TASKS_BL_FAIL = "# 任务\n- [x] T001 [SU] 建目录\n- [!] T002 [BL] 跑基线：复现偏差超容差\n- [-] T003 [TR] 跑处理组：基线未过检查点\n- [x] T004 [AN] 汇总\n"
RESULT = "# 结果\nmedian RMSE 0.20 vs mean 5.24（run.log）\n"
NEXT = "# 下一轮\n1. 对称污染下是否仍成立\n"


def run(root, *args):
    proc = subprocess.run([sys.executable, str(SCRIPT), *args, "--root", str(root)],
                          capture_output=True, text=True, encoding="utf-8")
    try:
        return proc.returncode, json.loads(proc.stdout)
    except ValueError:
        return proc.returncode, {"stdout": proc.stdout, "stderr": proc.stderr}


def check(label, cond, detail=""):
    print(("  PASS  " if cond else "  FAIL  ") + label + ("" if cond else f" — {detail}"))
    if not cond:
        failures.append(label)


def fill(path, text):
    path.write_text(text, encoding="utf-8")


def phase(root, slug):
    return run(root, "check", slug)[1].get("phase")


def main():
    tmp = Path(tempfile.mkdtemp(prefix="verify-space-test-"))
    try:
        root = tmp / "proj"
        (root / "idea-stage").mkdir(parents=True)
        (root / "refine-logs").mkdir()
        mainline = root / "idea-stage" / "IDEA_REPORT.md"
        fill(mainline, "# mainline report\n")
        original = mainline.read_bytes()

        code, out = run(root, "new", "--title", "中位数在离群点下更稳", "--slug", "median-robust", "--source", "用户原话")
        check("new creates a verification in the specify phase",
              code == 0 and out.get("slug") == "median-robust" and out.get("phase") == "specify", out)
        idea = root / "_verify-idea-stage" / "median-robust"
        plan = root / "_verify-refine-logs" / "median-robust"
        rendered = [idea / "HYPOTHESIS.md", idea / "RESULT.md", idea / "NEXT_IDEAS.md",
                    plan / "PILOT_PLAN.md", plan / "PILOT_TASKS.md"]
        check("all five templates render into _verify- mirrors", all(p.is_file() for p in rendered))
        check("templates have no unrendered placeholders",
              all("{{" not in p.read_text(encoding="utf-8") for p in rendered))
        check("no warning when the mainline exists", "warning" not in out, out)

        code, out = run(root, "new", "--title", "again", "--slug", "median-robust")
        check("slug collision gets a suffix", code == 0 and out.get("slug") == "median-robust-2", out)
        code, out = run(root, "new", "--title", "bad", "--slug", "Bad Slug")
        check("invalid slug is refused", code == 1 and out.get("ok") is False, out)

        fill(idea / "HYPOTHESIS.md", HYP + "数据规模【待澄清】\n")
        check("an open clarification keeps the phase at specify", phase(root, "median-robust") == "specify")
        fill(idea / "HYPOTHESIS.md", HYP)
        check("a filled hypothesis moves to plan", phase(root, "median-robust") == "plan")
        fill(plan / "PILOT_PLAN.md", PLAN_OPEN)
        check("an unchecked gate keeps the phase at plan", phase(root, "median-robust") == "plan")
        fill(plan / "PILOT_PLAN.md", "# 方案\n没有闸门\n")
        check("a plan with its gates deleted stays at plan", phase(root, "median-robust") == "plan")
        fill(plan / "PILOT_PLAN.md", PLAN_DONE)
        check("resolved gates move to tasks", phase(root, "median-robust") == "tasks")
        fill(plan / "PILOT_TASKS.md", TASKS_RUN)
        code, out = run(root, "check", "median-robust")
        check("pending tasks move to run and name the next task",
              out.get("phase") == "run" and out["tasks"]["next_task"] == "T002" and out["gates"]["open"] == 0, out)

        code, out = run(root, "conclude", "median-robust", "--verdict", "supported", "--summary", "s")
        check("conclude refuses pending tasks and unfilled docs",
              code == 1 and "pending" in out.get("error", "") and "RESULT.md" in out.get("error", ""), out)

        fill(plan / "PILOT_TASKS.md", TASKS_AN)
        check("only analysis tasks left moves to analyze", phase(root, "median-robust") == "analyze")
        fill(plan / "PILOT_TASKS.md", TASKS_DONE)
        check("a template-only RESULT.md stays at analyze", phase(root, "median-robust") == "analyze")
        fill(idea / "RESULT.md", RESULT)
        check("a filled RESULT.md moves to next", phase(root, "median-robust") == "next")
        fill(idea / "NEXT_IDEAS.md", NEXT)
        check("filled next ideas move to conclude", phase(root, "median-robust") == "conclude")

        code, out = run(root, "dir", "median-robust", "pilot")
        check("dir creates the experiment mirror",
              code == 0 and (root / "_verify-pilot" / "median-robust").is_dir()
              and out.get("path") == "_verify-pilot/median-robust", out)
        code, out = run(root, "dir", "median-robust", "../escape")
        check("dir refuses path traversal", code == 1, out)
        code, out = run(root, "dir", "median-robust", "_verify-pilot")
        check("dir refuses a mirror as the mainline name", code == 1, out)
        fill(root / "_verify-pilot" / "median-robust" / "run.py", "print('ok')\n")

        code, out = run(root, "conclude", "median-robust", "--verdict", "supported", "--summary", "中位数更稳")
        check("conclude records the verdict", code == 0 and out.get("verdict") == "supported", out)
        check("an untouched mainline is reported as untouched", out.get("mainline_touched") is False, out)
        check("a concluded verification waits for the user's decision", phase(root, "median-robust") == "decide")

        code, out = run(root, "new", "--title", "对称污染", "--slug", "sym-contam", "--parent", "median-robust")
        check("a child verification records its parent", code == 0 and out.get("parent") == "median-robust", out)
        code, out = run(root, "show", "median-robust")
        check("the parent lists its child", "sym-contam" in out["verification"].get("children", []), out)
        code, out = run(root, "new", "--title", "x", "--parent", "no-such")
        check("an unknown parent is refused", code == 1, out)

        run(root, "new", "--title", "baseline fails", "--slug", "blfail")
        b_idea, b_plan = root / "_verify-idea-stage" / "blfail", root / "_verify-refine-logs" / "blfail"
        fill(b_idea / "HYPOTHESIS.md", HYP)
        fill(b_plan / "PILOT_PLAN.md", PLAN_DONE)
        fill(b_plan / "PILOT_TASKS.md", TASKS_BL_FAIL)
        fill(b_idea / "RESULT.md", RESULT)
        fill(b_idea / "NEXT_IDEAS.md", NEXT)
        code, out = run(root, "conclude", "blfail", "--verdict", "supported", "--summary", "x")
        check("a failed baseline task blocks a supported verdict", code == 1 and "baseline" in out.get("error", ""), out)
        fill(root / "idea-stage" / "NEW_FILE.md", "polluted\n")
        code, out = run(root, "conclude", "blfail", "--verdict", "inconclusive", "--summary", "基线未复现")
        check("mainline changes during a verification are reported",
              code == 0 and out.get("mainline_touched") is True
              and "idea-stage/NEW_FILE.md" in out.get("mainline_changes", []), out)

        code, out = run(root, "promote", "blfail", "--to", "research-refine")
        check("promote refuses an inconclusive verification", code == 1, out)
        code, out = run(root, "promote", "blfail", "--to", "research-refine", "--force")
        check("a forced promotion needs a reason", code == 1, out)
        code, out = run(root, "promote", "median-robust", "--to", "research-refine")
        check("promote writes PROMOTION.md for a supported verification",
              code == 0 and (idea / "PROMOTION.md").is_file(), out)
        check("PROMOTION.md has no unrendered placeholders",
              "{{" not in (idea / "PROMOTION.md").read_text(encoding="utf-8"))
        check("the mainline report is byte-identical after promotion", mainline.read_bytes() == original)

        run(root, "archive", "blfail", "--reason", "基线未复现")
        code, out = run(root, "list")
        items = {v["slug"]: v for v in out.get("verifications", [])}
        check("list hides archived verifications and reports phases",
              "blfail" not in items and items.get("median-robust", {}).get("phase") == "closed"
              and items.get("sym-contam", {}).get("phase") == "specify", out)
        code, out = run(root, "list", "--all")
        check("list --all includes archived ones", "blfail" in [v["slug"] for v in out.get("verifications", [])], out)

        written = [p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file()]
        stray = [p for p in written
                 if not p.startswith("_verify-") and p not in ("idea-stage/IDEA_REPORT.md", "idea-stage/NEW_FILE.md")]
        check("nothing is written outside _verify- mirrors", not stray, stray)

        code, out = run(root, "new", "--title", "real templates", "--slug", "real")
        docs = {k: root / v for k, v in out["docs"].items()}

        def fill_placeholders(path):
            lines = path.read_text(encoding="utf-8").splitlines()
            fill(path, "\n".join("已填写" if "【TODO】" in l else l.replace("- [ ] G", "- [x] G") for l in lines) + "\n")

        seen = []
        for key in ("hypothesis", "plan", "tasks"):
            fill_placeholders(docs[key])
            seen.append(phase(root, "real"))
        fill(docs["tasks"], docs["tasks"].read_text(encoding="utf-8") + "- [x] T001 [SU] 准备\n- [x] T002 [AN] 汇总\n")
        seen.append(phase(root, "real"))
        for key in ("result", "next"):
            fill_placeholders(docs[key])
            seen.append(phase(root, "real"))
        check("real templates clear phase by phase once their placeholders are filled",
              seen == ["plan", "tasks", "tasks", "analyze", "next", "conclude"], seen)

        aris = tmp / "aris"
        (aris / "skills").mkdir(parents=True)
        (aris / "tools").mkdir()
        fill(aris / "AGENT_GUIDE.md", "x")
        code, out = run(aris, "new", "--title", "x")
        check("refuses to run inside the ARIS repository", code == 1 and "ARIS repository" in out.get("error", ""), out)

        bare = tmp / "bare"
        bare.mkdir()
        code, out = run(bare, "new", "--title", "x")
        check("warns when no ARIS mainline exists", code == 0 and "warning" in out, out)
    finally:
        shutil.rmtree(tmp, ignore_errors=True)
    print("OK" if not failures else f"{len(failures)} check(s) failed")
    return 0 if not failures else 1


if __name__ == "__main__":
    sys.exit(main())
