"""test_bench_solutions.py — deterministic conformance for bench.py --solutions (#4).

No LLM: builds synthetic SolutionAdapter trees + runs bench.py --solutions, asserting exit code and JSON
buckets (CONFORMANT / MISS / SOLUTION_BLIND_SPOT). Mirrors tests/test_recall_gate.py's synthetic-env pattern."""
import json
import os
import shutil
import subprocess
import sys
import tempfile

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def _solutions(solutions_root=None):
    cmd = [sys.executable, os.path.join(ROOT, "bench.py"), "--solutions"]
    if solutions_root:
        cmd += ["--solutions-root", solutions_root]
    r = subprocess.run(cmd, capture_output=True, text=True)
    return r, json.loads(r.stdout)


def _env(tmp, solve_body):
    """Build domains/security/solutions/test-sol + reuse the real S4 corpus cell fixture."""
    sol = os.path.join(tmp, "domains", "security", "solutions", "test-sol")
    os.makedirs(os.path.join(sol, "cells"), exist_ok=True)
    with open(os.path.join(sol, "solution.json"), "w", encoding="utf-8") as fh:
        json.dump({"id": "test-sol", "class": "S4", "rung": "located-suggestion",
                   "exec": ["python3", "domains/security/solutions/test-sol/solve.py"]}, fh)
    with open(os.path.join(sol, "solve.py"), "w", encoding="utf-8") as fh:
        fh.write(solve_body)
    art_src = os.path.join(ROOT, "domains", "security", "solutions", "sql-parameterize",
                           "cells", "S4-kb-spaceids-sqli.json")
    shutil.copy(art_src, os.path.join(sol, "cells", "S4-kb-spaceids-sqli.json"))
    return tmp


GOOD_SOLVE = '''#!/usr/bin/env python3
import json, sys
def main():
    payload = json.load(sys.stdin)
    finding = payload.get("finding", {})
    content = payload.get("file_content", "")
    if finding.get("class") != "S4" or "sql.raw" not in content:
        json.dump(None, sys.stdout); sys.stdout.write("\\n"); return
    json.dump({"adapter": "test-sol", "class": "S4", "rung": "located-suggestion", "status": "ok",
        "location": {"file": finding.get("file", ""), "line": finding.get("line", 0),
                     "symbol": finding.get("symbol", "")},
        "suggestion": "Replace with inArray(kbArticles.spaceId, spaceIds) — concrete parameterized replacement.",
        "patch": None, "coverage": {"unresolved": []}}, sys.stdout)
    sys.stdout.write("\\n")
if __name__ == "__main__":
    main()
'''

BAD_GENERIC_VULN = '''#!/usr/bin/env python3
import json, sys
def main():
    payload = json.load(sys.stdin)
    finding = payload.get("finding", {})
    if finding.get("class") != "S4":
        json.dump(None, sys.stdout); sys.stdout.write("\\n"); return
    json.dump({"adapter": "test-sol", "class": "S4", "rung": "located-suggestion", "status": "ok",
        "location": {"file": finding.get("file", ""), "line": finding.get("line", 0),
                     "symbol": finding.get("symbol", "")},
        "suggestion": "Use parameterized queries instead of string concatenation.",
        "patch": None, "coverage": {"unresolved": []}}, sys.stdout)
    sys.stdout.write("\\n")
if __name__ == "__main__":
    main()
'''

BAD_NO_SAFE_ABSTAIN = '''#!/usr/bin/env python3
import json, sys
def main():
    payload = json.load(sys.stdin)
    finding = payload.get("finding", {})
    if finding.get("class") != "S4":
        json.dump(None, sys.stdout); sys.stdout.write("\\n"); return
    json.dump({"adapter": "test-sol", "class": "S4", "rung": "located-suggestion", "status": "ok",
        "location": {"file": finding.get("file", ""), "line": finding.get("line", 0),
                     "symbol": finding.get("symbol", "")},
        "suggestion": "Replace with inArray(kbArticles.spaceId, spaceIds) — concrete parameterized replacement.",
        "patch": None, "coverage": {"unresolved": []}}, sys.stdout)
    sys.stdout.write("\\n")
if __name__ == "__main__":
    main()
'''


def test_real_sql_parameterize_conforms():
    r, out = _solutions()
    assert r.returncode == 0, f"real sql-parameterize must be green; misses={out['misses']}"
    assert out["solution_conformance"] == 1.0, out
    rows = [row for row in out["rows"] if row.get("adapter") == "sql-parameterize"]
    assert rows and all(row["status"] == "CONFORMANT" for row in rows), rows
    probes = {row["probe"] for row in rows}
    assert probes == {"vuln", "safe", "nonmatching"}


def test_solution_blind_spots_are_reported_not_silent():
    r, out = _solutions()
    assert r.returncode == 0
    blind = {b["class"] for b in out["solution_blind_spots"]}
    assert "S1" in blind and "S4" not in blind, f"blind-spot classes drifted: {blind}"
    blind_rows = [row for row in out["rows"] if row.get("status") == "SOLUTION_BLIND_SPOT"]
    assert blind_rows and {row["class"] for row in blind_rows} == blind


def test_nonconformant_generic_vuln_is_red(tmp_path):
    root = _env(str(tmp_path), BAD_GENERIC_VULN)
    r, out = _solutions(root)
    assert r.returncode != 0, "generic located-suggestion on vuln must fail the gate"
    assert any(m.get("probe") == "vuln" for m in out["misses"]), out["misses"]


def test_nonconformant_safe_suggestion_is_red(tmp_path):
    tmp = tempfile.mkdtemp(prefix="sg_solutions_")
    try:
        dst = os.path.join(tmp, "domains", "security", "solutions")
        shutil.copytree(os.path.join(ROOT, "domains", "security", "solutions"), dst)
        solve = os.path.join(dst, "sql-parameterize", "solve.py")
        with open(solve, "w", encoding="utf-8") as fh:
            fh.write(BAD_NO_SAFE_ABSTAIN)
        r, out = _solutions(tmp)
        assert r.returncode != 0, "suggestion on safe.ts must fail the gate"
        assert any(m.get("probe") == "safe" for m in out["misses"]), out["misses"]
    finally:
        shutil.rmtree(tmp, ignore_errors=True)


def test_synthetic_conformant_adapter_is_green(tmp_path):
    root = _env(str(tmp_path), GOOD_SOLVE)
    r, out = _solutions(root)
    assert r.returncode == 0, f"synthetic conformant adapter must pass; misses={out['misses']}"
    rows = [row for row in out["rows"] if row.get("adapter") == "test-sol"]
    assert rows and all(row["status"] == "CONFORMANT" for row in rows)
