#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Runs the reference verifier over every published vector suite.

Prints a JSON report (per suite: pass, fail, skip; totals; all_passed) and
exits non-zero if any case fails. A case passes when the verdict matches and
every expected finding name appears in the verifier's findings; a case whose
expected verdict is `verified` must have no findings at all.

Each suite's expected.json names the trust anchors to pin (`trust_anchors`,
relative to the suite directory); a case may override it, and `null` means
"run without trust anchors" (the verdict is then `refused`).

A case may name a producer profile (`profile`, relative to the suite
directory); the verifier then runs with it (PRF-01).

Without OpenSSL 1.1 or later the token signature cannot be checked, so every
case whose export carries a timestamp token is skipped and counted as such,
never passed.

Usage: python3 -I run_vectors.py [vectors_dir] [--openssl PATH]
Licensed Apache-2.0.
"""
from __future__ import annotations

import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from adcr_verify import find_openssl, verify  # noqa: E402


def has_tokens(export: dict) -> bool:
    return any(isinstance(a, dict) and a.get("token_b64") for a in (export.get("envelope") or {}).get("anchors") or [])


def main(vectors_dir, openssl=None):
    tool = find_openssl(openssl)
    report = {"openssl": tool, "suites": {}, "totals": {"pass": 0, "fail": 0, "skip": 0}, "all_passed": True}
    for suite in sorted(os.listdir(vectors_dir)):
        sd = os.path.join(vectors_dir, suite)
        exp_path = os.path.join(sd, "expected.json")
        if not os.path.isfile(exp_path):
            continue
        with open(exp_path, encoding="utf-8") as fh:
            expected = json.load(fh)
        res = {"pass": 0, "fail": 0, "skip": 0, "failures": [], "skipped": []}
        for name, want in expected["cases"].items():
            cp = os.path.join(sd, "cases", name + ".json")
            if not os.path.isfile(cp):
                res["skip"] += 1
                res["skipped"].append({"case": name, "why": "case file missing"})
                continue
            with open(cp, encoding="utf-8") as fh:
                export = json.load(fh)
            anchors_rel = want.get("trust_anchors", expected.get("trust_anchors"))
            trust = None
            if anchors_rel:
                with open(os.path.join(sd, anchors_rel), "rb") as fh:
                    trust = fh.read()
            if tool is None and has_tokens(export) and trust is not None:
                res["skip"] += 1
                res["skipped"].append({"case": name, "why": "needs OpenSSL 1.1+ to check the token signature"})
                continue
            prof = None
            if want.get("profile"):
                with open(os.path.join(sd, want["profile"]), encoding="utf-8") as fh:
                    prof = json.load(fh)
            got = verify(export, trust, tool or False, prof)
            names = {f["finding"] for f in got["findings"]}
            ok = got["verdict"] == want["verdict"] and set(want["findings"]) <= names
            if want["verdict"] == "verified":
                ok = ok and not names
            if ok:
                res["pass"] += 1
            else:
                res["fail"] += 1
                res["failures"].append({"case": name, "want": want,
                                        "got": {"verdict": got["verdict"], "findings": sorted(names)}})
        report["suites"][suite] = res
        for k in ("pass", "fail", "skip"):
            report["totals"][k] += res[k]
    report["all_passed"] = report["totals"]["fail"] == 0
    print(json.dumps(report, indent=2))
    return 0 if report["all_passed"] else 1


if __name__ == "__main__":
    args = sys.argv[1:]
    ossl = None
    if "--openssl" in args:
        i = args.index("--openssl")
        ossl = args[i + 1]
        del args[i:i + 2]
    here = os.path.dirname(os.path.abspath(__file__))
    sys.exit(main(args[0] if args else os.path.join(here, "vectors"), ossl))
