#!/usr/bin/env python3
"""Verify the DecayGuard public anchor chain — no trust required.

Anyone can run this against the public site. It downloads
``anchors.jsonl`` plus every anchored day's artifacts, recomputes all
hashes, and checks three things:

  1. every anchor line's ``anchor_sha256`` matches its own content;
  2. every line's ``prev_anchor_sha256`` matches the line before it
     (rewriting any historical day would break every later line);
  3. every artifact served today hashes to the value anchored on its
     publish day — i.e. the track record you see now is the one that
     existed then, bit for bit.

No AWS credentials, no special access: everything is fetched over
plain HTTPS from the public site. This script is the point of the
anchor chain — "we hash things" only matters if *you* can check.

  python -m scripts.verify_anchors                      # live site
  python -m scripts.verify_anchors --base https://.../  # any mirror
  python -m scripts.verify_anchors --local out/         # local build
"""
from __future__ import annotations

import argparse
import base64
import hashlib
import json
import os
import sys
import urllib.request
from pathlib import Path

DEFAULT_BASE = "https://decayguard.deepfieldlabs.dev/data"


def _sha(doc: dict) -> str:
    return hashlib.sha256(
        json.dumps(doc, sort_keys=True, separators=(",", ":")).encode()
    ).hexdigest()


def _fetch(base: str, rel: str, local: Path | None) -> bytes | None:
    if local is not None:
        p = local / rel
        return p.read_bytes() if p.exists() else None
    req = urllib.request.Request(f"{base}/{rel}")
    # Optional HTTP basic auth (DG_VERIFY_AUTH="user:pass") — lets the
    # pre-launch integrity sweep run against a still-gated mirror. The
    # public site needs no auth; leaving the variable unset changes
    # nothing.
    auth = os.environ.get("DG_VERIFY_AUTH")
    if auth:
        token = base64.b64encode(auth.encode()).decode()
        req.add_header("Authorization", f"Basic {token}")
    # Some Python installs (notably virtualenvs on macOS) ship without
    # a CA trust store and fail every HTTPS fetch with
    # CERTIFICATE_VERIFY_FAILED. Use certifi's bundle when it is
    # importable; otherwise fall back to the platform default. No
    # third-party requirement is introduced either way.
    ctx = None
    try:
        import ssl
        import certifi
        ctx = ssl.create_default_context(cafile=certifi.where())
    except Exception:
        pass
    try:
        with urllib.request.urlopen(req, timeout=30, context=ctx) as r:
            return r.read()
    except Exception:
        return None


def verify(base: str, local: Path | None = None,
           deep: bool = True) -> tuple[bool, list[str]]:
    problems: list[str] = []
    raw = _fetch(base, "anchors.jsonl", local)
    if raw is None:
        return False, ["anchors.jsonl not reachable"]
    lines = [ln for ln in raw.decode().splitlines() if ln.strip()]
    prev_sha = None
    for i, ln in enumerate(lines):
        rec = json.loads(ln)
        day = rec.get("day", f"line {i}")
        body = {k: v for k, v in rec.items() if k != "anchor_sha256"}
        if _sha(body) != rec.get("anchor_sha256"):
            problems.append(f"{day}: anchor self-hash mismatch")
        if rec.get("prev_anchor_sha256") != prev_sha:
            problems.append(f"{day}: chain link broken "
                            f"(prev does not match preceding line)")
        prev_sha = rec.get("anchor_sha256")

        if deep:
            for fname, want in (rec.get("artifacts") or {}).items():
                blob = _fetch(base, f"{day}/{fname}", local)
                if blob is None:
                    problems.append(f"{day}/{fname}: not served")
                    continue
                got = _sha(json.loads(blob))
                if got != want:
                    problems.append(
                        f"{day}/{fname}: served bytes hash {got[:12]}, "
                        f"anchored {want[:12]} — RESTATED")
    return not problems, problems


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--base", default=DEFAULT_BASE,
                    help="base URL serving anchors.jsonl and <day>/ dirs")
    ap.add_argument("--local", type=Path, default=None,
                    help="verify a local publish dir instead of HTTP")
    ap.add_argument("--chain-only", action="store_true",
                    help="skip per-artifact re-download (fast)")
    args = ap.parse_args(argv)

    ok, problems = verify(args.base, args.local,
                          deep=not args.chain_only)
    src = args.local or args.base
    if ok:
        print(f"anchor chain VERIFIED against {src} — every published "
              f"day is exactly what was anchored on its publish day")
        return 0
    print(f"anchor chain FAILED against {src}:", file=sys.stderr)
    for p in problems:
        print(f"  {p}", file=sys.stderr)
    return 1


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