#!/usr/bin/env python3
"""GRIP conformance suite runner.

Asserts that the happy-path vector verifies clean under all eight rules,
and that each negative vector FAILS EXACTLY THE RULES IT IS DESIGNED TO
FAIL. A negative vector that fails the wrong rule is a suite failure:
passing by accident is not passing (§4.7).
"""
from __future__ import annotations

import glob
import json
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "tools"))
from grip_verify import Verifier, load_bundle, load_objects  # noqa: E402

VECTORS = os.path.join(HERE, "..", "vectors")


def run_one(name: str) -> dict:
    d = os.path.join(VECTORS, name)
    exp = json.load(open(os.path.join(d, "expected.json")))
    rep = Verifier(load_objects(os.path.join(d, "objects")),
                   load_bundle(os.path.join(d, "trust-bundle.json"))).run().report()
    failed = {int(k[4:]) for k, v in rep["verdicts"].items() if v["verdict"] == "fail"}
    problems = []

    if exp.get("expect_clean"):
        if not rep["clean"]:
            problems.append(f"expected clean, got failures on rules {sorted(failed)}")
    else:
        must = set(exp.get("must_fail_rules", []))
        missing = must - failed
        extra = failed - must
        if missing:
            problems.append(f"did NOT fail required rules {sorted(missing)}")
        if extra:
            problems.append(f"failed unintended rules {sorted(extra)} — vector is not surgical")
    return {"vector": name, "ok": not problems, "problems": problems,
            "failed_rules": sorted(failed), "description": exp.get("description", "")}


def schema_check() -> tuple[int, int]:
    """Every canonical object must validate against its published schema.
    A spec whose own vectors fail its own schemas is not a spec."""
    try:
        from jsonschema import Draft202012Validator
        from referencing import Registry, Resource
    except ImportError:
        print("  (jsonschema/referencing unavailable — schema stage skipped)\n")
        return (0, 0)
    sd = os.path.join(HERE, "..", "schema", "1.0-draft.2")
    reg = Registry().with_resources([
        (os.path.basename(f), Resource.from_contents(json.load(open(f))))
        for f in glob.glob(os.path.join(sd, "*.json"))])
    vals = {t_: Draft202012Validator(
        json.load(open(os.path.join(sd, f"{t_}.schema.json"))), registry=reg)
        for t_ in ("mandate", "grant", "receipt", "stop", "settlement", "context")}
    ok = tot = 0
    for f in sorted(glob.glob(os.path.join(VECTORS, "refund-happy-path", "objects", "*.json"))):
        o = json.load(open(f))
        tot += 1
        errs = list(vals[o["type"]].iter_errors(o))
        if errs:
            print(f"  [FAIL] {os.path.basename(f)}: {errs[0].message[:90]}")
        else:
            ok += 1
    print(f"  [{'PASS' if ok == tot else 'FAIL'}] schema validation: "
          f"{ok}/{tot} canonical objects validate against the published schemas\n")
    return (ok, tot)


def main():
    print("Stage 1 — schemas\n")
    s_ok, s_tot = schema_check()
    print("Stage 2 — chain verification\n")
    names = sorted(n for n in os.listdir(VECTORS)
                   if os.path.isdir(os.path.join(VECTORS, n))
                   and os.path.exists(os.path.join(VECTORS, n, "expected.json")))
    results = [run_one(n) for n in names]
    width = max(len(r["vector"]) for r in results)
    print(f"GRIP/1.0-draft.2 conformance suite — {len(results)} vectors\n")
    for r in results:
        mark = "PASS" if r["ok"] else "FAIL"
        rules = ",".join(str(x) for x in r["failed_rules"]) or "-"
        print(f"  [{mark}] {r['vector']:<{width}}  fails rules: {rules}")
        for p in r["problems"]:
            print(f"         ↳ {p}")
    bad = [r for r in results if not r["ok"]]
    print(f"\n{len(results) - len(bad)}/{len(results)} vectors behave as specified.")
    if s_tot and s_ok != s_tot:
        bad.append({"vector": "schemas"})
    if "--json" in sys.argv:
        json.dump(results, open(os.path.join(HERE, "results.json"), "w"), indent=2)
    return 1 if bad else 0


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