#!/usr/bin/env python3
"""Onchain Lab — independent trust-suite verifier (stdlib only; no repo deps).

A skeptic can download THIS ONE FILE and check, against the live site (or local files), that:
  1. the signal track-record hash chain is intact (each entry's hash = sha256 of its stored line
     with the derived hash/stamped fields removed — the exact preimage the site publishes);
  2. the published dataset Merkle root matches a fresh recompute of all_metrics.csv;
  3. (prints) the `ots verify` commands to check the Bitcoin OpenTimestamps proofs yourself.

  python verify_trust_suite.py                 # verify against the live site
  python verify_trust_suite.py --local         # verify local files next to this script (./data/)
  python verify_trust_suite.py --base https://onchainlab.net

Exit 0 = everything checks out. Exit 1 = a mismatch (with the offending entry/root printed).
"""
import argparse
import hashlib
import json
import re
import sys
import urllib.request
from pathlib import Path

# Same surgery the publisher uses: strip the derived hash + stamped fields, anchored to their
# top-level neighbours so a nested string can never be caught, and first-occurrence only.
_HASH_RE = re.compile(r'"hash":"[0-9a-f]{64}",(?="prev_hash":)')
_STAMPED_RE = re.compile(r',"stamped":(?:true|false)(?=}$)')


def _sha(b: bytes) -> str:
    return hashlib.sha256(b).hexdigest()


def _fetch(base: str, path: str, local: bool) -> str:
    if local:
        return (Path(__file__).resolve().parent / "data" / path).read_text(encoding="utf-8")
    # A browser-ish User-Agent so Cloudflare's default bot filter doesn't 403 the plain urllib UA.
    req = urllib.request.Request(
        f"{base}/data/{path}",
        headers={"User-Agent": "Mozilla/5.0 (verify_trust_suite.py; +https://onchainlab.net)"},
    )
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.read().decode("utf-8")


def verify_chain(text: str) -> tuple[bool, str]:
    prev = "0" * 64
    lines = [ln.rstrip("\r") for ln in text.split("\n") if ln.strip()]
    for i, line in enumerate(lines):
        e = json.loads(line)
        preimage = _STAMPED_RE.sub("", _HASH_RE.sub("", line, count=1), count=1)
        got = _sha(preimage.encode("utf-8"))
        if got != e.get("hash") or e.get("prev_hash") != prev:
            return False, f"entry {i} ({e.get('date')}): hash/prev mismatch"
        prev = e["hash"]
    return True, f"{len(lines)} entries, chain intact"


def _merkle_root(leaves: list[str]) -> str:
    if not leaves:
        return "0" * 64
    level = leaves[:]
    while len(level) > 1:
        if len(level) % 2:
            level.append(level[-1])
        level = [_sha(bytes.fromhex(level[i]) + bytes.fromhex(level[i + 1]))
                 for i in range(0, len(level), 2)]
    return level[0]


def verify_merkle(csv_text: str, integrity: dict) -> tuple[bool, str]:
    # Normalize CRLF -> LF (the root is computed on LF rows; urllib returns raw bytes so a
    # CRLF-served file would otherwise mismatch even when the content is identical).
    raw = [ln.rstrip("\r") for ln in csv_text.split("\n")]
    header, rows = raw[0], [r for r in raw[1:] if r.strip()]
    root = _merkle_root([_sha(r.encode("utf-8")) for r in rows])
    header_hash = _sha(header.encode("utf-8"))
    ok = root == integrity.get("merkle_root") and header_hash == integrity.get("header_hash")
    detail = f"{len(rows)} rows; root {root[:16]}… {'==' if root == integrity.get('merkle_root') else '!='} published"
    return ok, detail


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--base", default="https://onchainlab.net")
    ap.add_argument("--local", action="store_true")
    args = ap.parse_args()

    ok_all = True
    try:
        c_ok, c_msg = verify_chain(_fetch(args.base, "signal_log.jsonl", args.local))
        print(f"[{'OK ' if c_ok else 'FAIL'}] signal chain: {c_msg}")
        ok_all &= c_ok
    except Exception as e:
        print(f"[FAIL] signal chain: {type(e).__name__}: {e}"); ok_all = False

    try:
        integrity = json.loads(_fetch(args.base, "dataset_integrity.json", args.local))
        m_ok, m_msg = verify_merkle(_fetch(args.base, "all_metrics.csv", args.local), integrity)
        print(f"[{'OK ' if m_ok else 'FAIL'}] dataset merkle: {m_msg}")
        ok_all &= m_ok
    except Exception as e:
        print(f"[FAIL] dataset merkle: {type(e).__name__}: {e}"); ok_all = False

    print("\nTo verify the Bitcoin anchors yourself (install `pip install opentimestamps-client`):")
    print("  ots verify <date>.json.ots   # for each proof under /data/track_record/")
    print("\nVERDICT:", "chain OK / merkle OK — everything checks out" if ok_all else "MISMATCH — see above")
    return 0 if ok_all else 1


if __name__ == "__main__":
    raise SystemExit(main())
