#!/usr/bin/env python3
"""freeze_row_hash.py — canonical 'hash of the row' recipe for league freezes.

Mikey weld #102790: a hash pin is only as strong as the recipe beside it —
exactly which string gets hashed, field order and all. This file IS that
recipe, code-pinned. Any stranger can re-derive the pin with sha256sum alone.

RECIPE v1 (pin string, single line, no whitespace anywhere in the preimage):

  mlfreeze1|<season>|<day>|<close_block>|<muse_id>|<wallet>|<claim_ts>|<claimed_total>|<instruments>

  season         sheet["season"]   verbatim string           e.g. bankr-s1
  day            sheet["day"]      decimal integer           e.g. 1
  close_block    sheet["close_block"] decimal integer        e.g. 51000000
  muse_id        row["muse_id"]    verbatim                  e.g. muse_testpoison
  wallet         row["wallet"]     lowercase, 0x-prefixed
  claim_ts       row["claim_ts"]   verbatim ISO-8601 Z
  claimed_total  canonical decimal (see below)
  instruments    comma-joined "token:amount_raw", one leg per instrument,
                 token lowercase, amount_raw verbatim decimal-integer string,
                 legs sorted by token address (ascending)

  canonical decimal = format(Decimal(str(x)).normalize(), "f")
    9177926.0 -> "9177926"   4588778.170307 -> "4588778.170307"
    never scientific notation, no trailing zeros, "null" if absent

  HASH = "sha256:" + sha256(preimage_utf8).hexdigest()

  Stranger re-walk, zero deps:
    printf '%s' 'mlfreeze1|bankr-s1|1|51000000|muse_testpoison|0xcdac...|...' | sha256sum

Usage:
  freeze_row_hash.py sheet.json            list preimage + pin for every row
  freeze_row_hash.py sheet.json --pin ROW  commit line for one row (muse_id or 0-based index)
  freeze_row_hash.py sheet.json --verify sha256:<hex>
                                           blind reveal-walk: name the row(s) matching
  freeze_row_hash.py --selftest            run the pinned drill sheet end-to-end
"""
import hashlib, json, os, sys
from decimal import Decimal

HERE = __file__.rsplit("/", 1)[0]
RECIPE_FILE = os.path.join(HERE, "RECIPE-v1.txt")


def recipe_pin():
    """Fingerprint of the recipe itself — Mikey weld #102842:
    a hash nobody knows which recipe pinned it against is a lock
    without a key. RECIPE-v1.txt bytes verbatim are the preimage."""
    with open(RECIPE_FILE, "rb") as f:
        return "sha256:" + hashlib.sha256(f.read()).hexdigest()


def canon_decimal(x):
    if x is None:
        return "null"
    return format(Decimal(str(x)).normalize(), "f")


def preimage(sheet, row):
    instr = ",".join(
        f"{i['token'].lower()}:{i['amount_raw']}"
        for i in sorted(row["instruments"], key=lambda i: i["token"].lower())
    )
    return "|".join([
        "mlfreeze1",
        str(sheet["season"]), str(sheet["day"]), str(sheet["close_block"]),
        str(row["muse_id"]), row["wallet"].lower(),
        str(row["claim_ts"]), canon_decimal(row.get("claimed_total")), instr,
    ])


def row_hash(sheet, row):
    return "sha256:" + hashlib.sha256(preimage(sheet, row).encode()).hexdigest()


def pick_row(sheet, key):
    for i, r in enumerate(sheet["rows"]):
        if r["muse_id"] == key or str(i) == key:
            return r
    sys.exit(f"no row {key!r} in sheet")


def selftest():
    sheet = json.load(open(f"{HERE}/drill_e2e/sheet_final.json"))
    rows = sheet["rows"]

    # 1. pin the poison row, blind-verify recovers it by hash alone
    pin = row_hash(sheet, rows[1])
    hits = [r["muse_id"] for r in rows if row_hash(sheet, r) == pin]
    assert hits == ["muse_testpoison"], hits
    print(f"pin->reveal: {pin} resolves to muse_testpoison only")

    # 2. recipe is whitespace/key-order immune: shuffled dict, same hash
    shuffled = json.loads(json.dumps(rows[1]))  # round-trip reorders nothing semantically
    shuffled["instruments"] = list(reversed(shuffled["instruments"]))
    assert row_hash(sheet, shuffled) == pin
    print("key-order/instrument-order shuffle: SAME hash (recipe canonicalizes)")

    # 3. tamper-evidence: +1 wei on one leg -> different hash
    tampered = json.loads(json.dumps(rows[1]))
    tampered["instruments"][0]["amount_raw"] = str(
        int(tampered["instruments"][0]["amount_raw"]) + 1)
    assert row_hash(sheet, tampered) != pin
    print("+1 wei tamper: DIFFERENT hash (commitment holds)")

    # 4. claimed_total canonicalization: 9177926.0 -> "9177926"
    assert canon_decimal(9177926.0) == "9177926"
    assert canon_decimal(4588778.170307) == "4588778.170307"
    print("decimal canon: 9177926.0->9177926, 4588778.170307 unchanged")

    # 5. sha256sum equivalence proof
    pre = preimage(sheet, rows[1])
    print(f"preimage ({len(pre)} chars): {pre}")
    print("stranger check: printf '%s' '<preimage>' | sha256sum")

    # 6. recipe self-pin: fingerprint of RECIPE-v1.txt rides beside every pin
    rp = recipe_pin()
    assert rp == recipe_pin()  # deterministic
    assert len(rp) == len("sha256:") + 64
    print(f"recipe pin: {rp}  (stranger check: sha256sum RECIPE-v1.txt)")
    print("SELFTEST PASS")


def main():
    args = sys.argv[1:]
    if args == ["--selftest"]:
        return selftest()
    if args == ["--recipe"]:
        rp = recipe_pin()
        print(f"recipe pin: {rp}")
        print(f"  commits: RECIPE-v1.txt ({os.path.getsize(RECIPE_FILE)} bytes verbatim)")
        return
    sheet = json.load(open(args[0]))
    rp = recipe_pin()
    if "--verify" in args:
        want = args[args.index("--verify") + 1]
        hits = [r["muse_id"] for r in sheet["rows"] if row_hash(sheet, r) == want]
        print(json.dumps({"pin": want, "recipe": rp, "matches": hits,
                          "rows_checked": len(sheet["rows"])}))
        return
    if "--pin" in args:
        row = pick_row(sheet, args[args.index("--pin") + 1])
        h = row_hash(sheet, row)
        print(f"freeze pin {h}")
        print(f"  recipe:  {rp}")
        print(f"  commits: muse_id={row['muse_id']} wallet={row['wallet']} "
              f"close_block={sheet['close_block']} day={sheet['day']}")
        return
    print(f"recipe: {rp}  (sha256sum RECIPE-v1.txt)")
    for r in sheet["rows"]:
        print(f"[{r['muse_id']}]")
        print(f"  preimage: {preimage(sheet, r)}")
        print(f"  pin:      {row_hash(sheet, r)}")


if __name__ == "__main__":
    main()
