"""Runner de la demo de arnés determinista (traza sintética).

Solo usa la biblioteca estándar de Python 3.12. No hace llamadas de red ni
usa un LLM. Lee cases.json, carga la variante pedida (before.py o fixed.py)
desde esta misma carpeta, ejecuta TODOS los casos, compara de forma exacta y
escribe un informe JSON.

Códigos de salida: 0 si pasan todos los casos, 1 si falla alguno y 2 si
cases.json no respeta el conjunto fijado de 4 casos.
"""

import argparse
import copy
import importlib.util
import json
import sys
from pathlib import Path

HERE = Path(__file__).resolve().parent
EXPECTED_ROLES = {"regression": 1, "control": 2, "reserved": 1}


def load_variant(name: str):
    path = HERE / f"{name}.py"
    spec = importlib.util.spec_from_file_location(f"variant_{name}", path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module.parse_tool_response


def load_cases() -> list:
    with open(HERE / "cases.json", encoding="utf-8") as fh:
        cases = json.load(fh)
    roles: dict = {}
    for case in cases:
        roles[case["role"]] = roles.get(case["role"], 0) + 1
    if len(cases) != 4 or roles != EXPECTED_ROLES:
        print(
            f"cases.json no respeta el conjunto fijado (4 casos, {EXPECTED_ROLES}); "
            f"encontrado {len(cases)} casos, {roles}",
            file=sys.stderr,
        )
        sys.exit(2)
    return cases


def exact_match(observed, expected) -> bool:
    # Comparación canónica en JSON: distingue true de 1 y exige las mismas claves.
    try:
        return json.dumps(observed, sort_keys=True, ensure_ascii=False) == json.dumps(
            expected, sort_keys=True, ensure_ascii=False
        )
    except (TypeError, ValueError):
        return False


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Demo de arnés determinista con traza sintética (sin red, sin LLM)."
    )
    parser.add_argument("--variant", choices=["before", "fixed"], required=True)
    parser.add_argument("--output", required=True, help="Ruta del informe JSON")
    args = parser.parse_args()

    cases = load_cases()
    parse_tool_response = load_variant(args.variant)

    results = []
    for case in cases:
        observed, error = None, None
        try:
            observed = parse_tool_response(copy.deepcopy(case["input"]))
        except Exception as exc:  # un caso que lanza excepción cuenta como fallo
            error = f"{type(exc).__name__}: {exc}"
        passed = error is None and exact_match(observed, case["expected"])
        results.append(
            {
                "id": case["id"],
                "role": case["role"],
                "expected": case["expected"],
                "observed": observed,
                "passed": passed,
                "error": error,
            }
        )
        mark = "PASS" if passed else "FAIL"
        print(f"[{mark}] {case['id']} ({case['role']})")
        if not passed:
            print(f"    expected: {json.dumps(case['expected'], ensure_ascii=False)}")
            print(f"    observed: {json.dumps(observed, ensure_ascii=False, default=repr)}")
            if error:
                print(f"    error:    {error}")

    by_role: dict = {}
    for r in results:
        slot = by_role.setdefault(r["role"], {"passed": 0, "failed": 0})
        slot["passed" if r["passed"] else "failed"] += 1
    n_passed = sum(1 for r in results if r["passed"])
    counts = {
        "total": len(results),
        "passed": n_passed,
        "failed": len(results) - n_passed,
        "by_role": by_role,
    }

    report = {
        "variant": args.variant,
        "origin": "synthetic_fixture",
        "results": results,
        "counts": counts,
    }
    out = Path(args.output)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(
        json.dumps(report, ensure_ascii=False, indent=2, default=repr) + "\n",
        encoding="utf-8",
    )

    print(
        f"variant={args.variant} total={counts['total']} "
        f"passed={counts['passed']} failed={counts['failed']} -> {out}"
    )
    return 0 if counts["failed"] == 0 else 1


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