#!/usr/bin/env python3
"""wdy_versions.py — multi-version provenance ledger ("GitHub-style" versioning for WDY records).

Each submitted version gets its own WDY record; consecutive versions are linked
via a `parent` field, forming a verifiable creation timeline.

Usage:
  python wdy_versions.py new FILE --title T --type script|video|... --mode ai|live|hybrid
          [--ai-ratio 0.35] --key PATH.key [--record-id WDY-2026-0003] [--parent WDY-2026-0002-XXXXXX]
          [--tier standard|tsa|judicial] [--note "v2 dialogue revised"]
  python wdy_versions.py chain LEDGER.json          # print the version tree
  python wdy_versions.py verify RECORD_ID FILE      # verify one record against its file

Ledger: a local JSON file (default ./wdy-ledger.json) tracking every version you
issued: record_id, file hash, tier, parent, timestamp. Chain evidence = the tree.

Tier semantics:
  standard — hash + WDY co-signature + public verify page (free)
  tsa      — additionally request a trusted timestamp when sending to WDY (≈$2/record)
  judicial — additionally request judicial-chain anchoring / notarization at WDY review

Requires wdy_certify.py / wdy_ed25519.py in the same directory (or on PATH).
"""
import argparse, hashlib, json, os, subprocess, sys
from datetime import datetime, timezone
from pathlib import Path

HERE = Path(__file__).resolve().parent
CERTIFY = HERE / "wdy_certify.py"
if not CERTIFY.exists():
    for p in sys.path:
        c = Path(p) / "wdy_certify.py"
        if c.exists():
            CERTIFY = c; break

LEDGER_DEFAULT = Path.cwd() / "wdy-ledger.json"


def load_ledger(path):
    if path.exists():
        return json.loads(path.read_text(encoding="utf-8"))
    return {"records": []}


def save_ledger(path, data):
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def sha256_of(fpath):
    h = hashlib.sha256()
    with open(fpath, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()


def cmd_new(a):
    if not a.file or not os.path.exists(a.file):
        sys.exit(f"file not found: {a.file}")
    if not Path(a.key).exists():
        sys.exit(f"signing key not found: {a.key}  (create once: python wdy_certify.py keygen --out {a.key})")
    ledger = load_ledger(Path(a.ledger))
    rec_id = a.record_id
    if not rec_id:
        seq = len([r for r in ledger["records"]]) + 3  # start after demo/0001/0002
        sys.exit("refusing to auto-assign record ids — pass --record-id WDY-2026-%04d (keep your own numbering)" % seq)

    # resolve key_id by matching this key's public key against the published pub file
    key_id = a.key_id
    if not key_id:
        sys.path.insert(0, str(HERE))
        from wdy_ed25519 import ed25519_pub
        import base64 as _b64
        seed = bytes.fromhex(Path(a.key).read_text(encoding="utf-8").strip())
        pub_b64 = _b64.b64encode(ed25519_pub(seed)).decode()
        key_id = "2026-rot1"
        candidates = [HERE / "wdy-key.pub", Path.cwd() / "wdy-key.pub",
                      Path.cwd() / "pub.txt",
                      HERE.parent.parent.parent / ".well-known" / "wdy-key.pub"]
        pub_file = next((p for p in candidates if p.exists()), None)
        if pub_file.exists():
            for ln in pub_file.read_text(encoding="utf-8").splitlines():
                ln = ln.strip()
                if ln and not ln.startswith("#") and ln.partition("=")[2].strip().startswith(pub_b64[:20]):
                    key_id = ln.partition("=")[0].strip(); break
        print(f"  key_id    : {key_id} (matched from {pub_file.name})")

    mfile = a.file
    manifest = Path(f"{rec_id}.manifest.json")
    cmd = [sys.executable, str(CERTIFY), "issue", mfile,
           "--title", a.title, "--type", a.type, "--mode", a.mode,
           "--record-id", rec_id, "--key", a.key,
           "--out", str(manifest)]
    if a.mode == "hybrid":
        if a.ai_ratio is None:
            sys.exit("--ai-ratio required for hybrid")
        cmd += ["--ai-ratio", str(a.ai_ratio)]
    cmd += ["--tier", a.tier]
    cmd += ["--key-id", key_id]
    if a.tier == "tsa":
        cmd += ["--tsa-provider", a.tsa_provider or "(pending WDY review)",
                "--tsa-serial", "(pending)", "--tsa-ts", "(pending)"]
    if a.note:
        cmd += ["--claim", f"version_note:{a.note}"]
    if a.parent:
        cmd += ["--claim", f"parent_record:{a.parent}"]
    r = subprocess.run(cmd)
    if r.returncode != 0:
        sys.exit("issue failed")

    entry = {
        "record_id": rec_id,
        "parent": a.parent,
        "tier": a.tier,
        "title": a.title,
        "type": a.type,
        "mode": a.mode,
        "file": os.path.basename(mfile),
        "sha256": sha256_of(mfile),
        "manifest": manifest.name,
        "note": a.note or "",
        "created_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "status": "issued-locally (send manifest to WDY; tier confirmation in review email)",
    }
    ledger["records"].append(entry)
    save_ledger(Path(a.ledger), ledger)
    print(f"✓ {rec_id}  tier={a.tier}  parent={a.parent or '—'}")
    print(f"  manifest : {manifest.name}")
    print(f"  ledger   : {a.ledger}  ({len(ledger['records'])} versions)")
    print(f"  next     : email the manifest to WDY (subject CERTIFIED); keep the original file safe")


def cmd_chain(a):
    ledger = load_ledger(Path(a.ledger))
    recs = ledger["records"]
    if not recs:
        print("ledger empty"); return
    by_parent = {}
    for r in recs:
        by_parent.setdefault(r.get("parent"), []).append(r)
    roots = [r for r in recs if not r.get("parent")]
    def walk(r, depth=0):
        print("  " * depth + f"└─ {r['record_id']}  [{r['tier']}]  {r['title']}  {r['created_at']}"
              + (f"  ← {r['note']}" if r.get("note") else ""))
        for child in by_parent.get(r["record_id"], []):
            walk(child, depth + 1)
    for root in roots:
        walk(root)
    if not roots and recs:
        for r in recs:
            walk(r)


def cmd_verify(a):
    try:
        sys.path.insert(0, str(HERE))
        from wdy_verify import main as _  # noqa
    except Exception:
        pass
    r = subprocess.run([sys.executable, str(HERE / "wdy_verify.py"),
                        f"{a.record_id}.manifest.json", a.file,
                        "--pub", a.pub])
    sys.exit(r.returncode)


def main():
    ap = argparse.ArgumentParser(description="WDY multi-version provenance ledger")
    ap.add_argument("--ledger", default=str(LEDGER_DEFAULT))
    sub = ap.add_subparsers(dest="cmd", required=True)

    n = sub.add_parser("new", help="issue a new version record")
    n.add_argument("file")
    n.add_argument("--title", required=True)
    n.add_argument("--type", required=True, choices=["video", "script", "storyboard", "poster", "report", "document", "dataset", "webpage"])
    n.add_argument("--mode", required=True, choices=["ai", "live", "hybrid", "authored"])
    n.add_argument("--ai-ratio", default=None)
    n.add_argument("--key", required=True)
    n.add_argument("--record-id", required=True)
    n.add_argument("--parent", default=None, help="previous version's record_id")
    n.add_argument("--tier", default="standard", choices=["standard", "tsa", "judicial"])
    n.add_argument("--tsa-provider", default=None)
    n.add_argument("--note", default=None)
    n.add_argument("--key-id", default=None, help="defaults to auto-match from wdy-key.pub")
    n.set_defaults(fn=cmd_new)

    c = sub.add_parser("chain", help="print the version tree")
    c.set_defaults(fn=cmd_chain)

    v = sub.add_parser("verify", help="verify a record against its file")
    v.add_argument("record_id")
    v.add_argument("file")
    v.add_argument("--pub", default=str(HERE / "wdy-key.pub"))
    v.set_defaults(fn=cmd_verify)

    a = ap.parse_args()
    a.fn(a)


if __name__ == "__main__":
    main()
