"""CAIN-A75-039 offline verifier (rules embedded verbatim). CAIN-A75-039 fast-path-authority rules. Standard library only, no CAIN imports: embedded verbatim in the offline
verifier.

A fast model is trusted to act before the slower reviewer catches up. The review recomputes whether the fast path stayed
inside delegated authority:

  authority   the subagent's tools and cost ceiling are a subset of the parent's, and its expiry is not later
  fast path   the policy permits the fast path for these tools and this cost
  actions     every action uses an allowed tool, stays under the cost ceiling, and the count is within the limit
  audit       when post-audit is required, every action carries an audit reference, or the declared sample is met
  claims      every boolean the record asserts matches what this review computed

Verdict: SOUND, else UNSOUND with every failing check named. Malformed input is UNSOUND.
It reviews the fast-path record; it does not call the model.
"""
from __future__ import annotations

import math
import re

_ID = re.compile(r"^[A-Za-z0-9_.:\-]{1,80}$")


class Bad(ValueError):
    pass


def _num(v, what, lo=0.0, hi=1e12):
    if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) or not (lo <= v <= hi):
        raise Bad(f"{what} must be a finite number in [{lo}, {hi}]")
    return float(v)


def evaluate(record):
    checks, summary = [], {"actions": {}, "audited": 0}

    def c(cid, ok, detail=""):
        checks.append({"id": cid, "ok": bool(ok), "detail": detail})

    try:
        if not isinstance(record, dict) or record.get("schema") != "cain42.a75.fastpath.v1":
            raise Bad("not a cain42.a75.fastpath.v1 record")
        parent, sub, fp, pol = record.get("parent"), record.get("subagent"), record.get("fast_path"), record.get("policy")
        if not all(isinstance(x, dict) for x in (parent, sub, fp, pol)):
            raise Bad("parent, subagent, fast_path and policy must be objects")
        now = _num(fp.get("now"), "fast_path.now")
        pt = set(parent.get("tools") or [])
        st = set(sub.get("tools") or [])
        c("authority.tools_subset", st <= pt, f"subagent tools {sorted(st)} subset of parent {sorted(pt)}")
        c("authority.cost_subset", _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") <= _num(parent.get("max_cost_usd"), "parent.max_cost_usd"),
          "subagent cost ceiling is within the parent")
        pexp = _num(parent.get("expires_at"), "parent.expires_at")
        sexp = _num(sub.get("expires_at"), "subagent.expires_at")
        c("authority.not_expired", sexp > now, f"expires {sexp} > now {now}")
        c("authority.expiry_bounded", sexp <= pexp, f"subagent expiry {sexp} <= parent {pexp}")

        c("fast_path.enabled", fp.get("enabled") is True, "the fast path is enabled")
        c("fast_path.reason", isinstance(fp.get("reason"), str) and bool(fp["reason"].strip()), "a reason is stated")
        fp_tools = pol.get("fast_path_allowed_tools") if isinstance(pol.get("fast_path_allowed_tools"), list) else []
        fp_cost = _num(pol.get("fast_path_max_cost"), "policy.fast_path_max_cost", 0.0)
        c("fast_path.tools_allowed", st <= set(fp_tools), f"tools {sorted(st)} subset of {sorted(fp_tools)}")
        c("fast_path.cost_bounded", _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") <= fp_cost + 1e-9,
          f"ceiling {sub.get('max_cost_usd')} <= {fp_cost}")

        actions = record.get("actions")
        if not isinstance(actions, list) or len(actions) > 10000:
            raise Bad("actions must be a list")
        limit = pol.get("max_actions")
        if isinstance(limit, bool) or not isinstance(limit, int) or limit < 1:
            raise Bad("policy.max_actions must be a positive integer")
        c("actions.count", len(actions) <= limit, f"{len(actions)} <= {limit}")
        seen, total = set(), 0.0
        for a in actions:
            if not isinstance(a, dict) or not isinstance(a.get("id"), str) or not _ID.match(a["id"]):
                raise Bad("an action needs an id")
            if a["id"] in seen:
                raise Bad(f"duplicate action {a['id']}")
            seen.add(a["id"])
            c(f"{a['id']}.tool", a.get("tool") in st, f"tool {a.get('tool')!r} in subagent authority")
            cost = _num(a.get("cost_usd"), f"{a['id']}.cost_usd", 0.0)
            total += cost
            c(f"{a['id']}.within_cost", cost <= _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") + 1e-9, "action cost within the ceiling")
            audited = isinstance(a.get("audit_ref"), str) and bool(a["audit_ref"])
            if audited:
                summary["audited"] += 1
            summary["actions"][a["id"]] = {"tool": a.get("tool"), "cost": cost, "audited": audited}
        c("actions.total_cost", total <= _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") + 1e-9,
          f"total {round(total, 6)} <= ceiling")

        sample = _num(fp.get("audit_sample_rate", 0.0), "fast_path.audit_sample_rate", 0.0, 1.0)
        required = pol.get("require_post_audit") is True
        if required:
            need = math.ceil(sample * len(actions) - 1e-9)
            c("audit.sample_met", summary["audited"] >= need, f"{summary['audited']} audited, sampled need {need}")
        else:
            c("audit.not_required", True, "post-audit is not required by policy")

        claims = record.get("claims") if isinstance(record.get("claims"), dict) else {}
        computed = {
            "within_parent_authority": st <= pt and _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") <= _num(parent.get("max_cost_usd"), "parent.max_cost_usd"),
            "fast_path_bounded": st <= set(fp_tools) and _num(sub.get("max_cost_usd"), "subagent.max_cost_usd") <= fp_cost + 1e-9,
            "audited": (not required) or summary["audited"] >= math.ceil(sample * len(actions) - 1e-9),
        }
        for key in sorted(claims):
            c(f"claims.{key}", key in computed and claims[key] == computed[key], f"claimed {claims[key]!r}, computed {computed.get(key)!r}")
        summary["computed"] = computed
    except Bad as e:
        checks.append({"id": "input.well_formed", "ok": False, "detail": str(e)})
        return "UNSOUND", checks, summary
    except Exception as e:  # noqa: BLE001
        checks.append({"id": "input.error", "ok": False, "detail": f"{type(e).__name__}: {e}"[:200]})
        return "UNSOUND", checks, summary
    return ("SOUND" if all(x["ok"] for x in checks) else "UNSOUND"), checks, summary


# ---------------------------------------------------------------------------- offline verifier (no CAIN imports)
# Every name below is prefixed _kit_verifier_ / _b64 / _hl / _json / _sys so it cannot shadow a name in the rules above.
import base64 as _b64, hashlib as _hl, json as _json, sys as _sys
_KIT_VERIFIER_GENESIS = _hl.sha256(b"cain42.a75.fastpath.genesis").hexdigest()


def _kit_verifier_canon(o):
    return _json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()


def _kit_verifier_sha(o):
    return _hl.sha256(_kit_verifier_canon(o)).hexdigest()


def _kit_verifier_main(argv):
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    if len(argv) < 2:
        print("usage: verify_offline.py run.json --pinned-key BASE64"); return 2
    run = _json.load(open(argv[1]))
    pin = argv[argv.index("--pinned-key") + 1] if "--pinned-key" in argv else None
    problems, prev, recs = [], _KIT_VERIFIER_GENESIS, run.get("records") or []
    for i, r in enumerate(recs):
        body = {k: v for k, v in r.items() if k not in ("record_hash", "signature_b64")}
        if _kit_verifier_sha(body) != r.get("record_hash"): problems.append(f"record {i}: record_hash does not match")
        if body.get("subject_sha256") != _kit_verifier_sha(body.get("subject")): problems.append(f"record {i}: subject_sha256 does not match")
        if pin and r.get("public_key_b64") != pin: problems.append(f"record {i}: different key than the pinned one")
        try:
            Ed25519PublicKey.from_public_bytes(_b64.b64decode(pin or r.get("public_key_b64"))).verify(
                _b64.b64decode(r.get("signature_b64", "")), str(r.get("record_hash")).encode())
        except Exception:
            problems.append(f"record {i}: signature does not verify")
        if r.get("prev_record_hash") != prev: problems.append(f"record {i}: chain link broken")
        v, checks, _ = evaluate(r.get("subject"))
        if v != r.get("verdict") or checks != r.get("checks"):
            problems.append(f"record {i}: re-running the rules gives {v}, the record says {r.get('verdict')}")
        prev = r.get("record_hash")
    if run.get("run_hash") != _kit_verifier_sha([r.get("record_hash") for r in recs]): problems.append("run_hash does not match")
    for p in problems: print("  -", p)
    if not pin: print("WARNING: no --pinned-key given; the signature was checked against the key inside the file")
    print("VERDICT:", "VERIFIED" if not problems else "TAMPERED OR INVALID", f"(CAIN-A75-039, {len(recs)} record(s), verdicts {run.get('verdicts')})")
    return 0 if not problems else 1


if __name__ == "__main__":
    _sys.exit(_kit_verifier_main(_sys.argv))
