"""Daily ledger fingerprints (hash chain). Runs after engine.py. Paper trading only.
For every bot: doc = its full ledger (fills, daily cash/holdings/equity, holdings, pending orders, order receipts).
bot_hash = sha256(canonical({"bot", "date", "prev", "doc"})), prev = that bot's hash in the previous daily bundle.
root     = sha256(canonical({"date", "prev_root", "bots": {bot: bot_hash}})).
canonical = JSON with sorted keys, no spaces, UTF-8 (json.dumps(sort_keys=True, separators=(",", ":"), ensure_ascii=False)).
Writes site/data/ledger/<bot>.json, site/data/fingerprints/<date>.json + latest.json, and public-ledger/<date>/."""
import json, os, glob, hashlib, datetime as dt, shutil
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))); S = os.path.join(ROOT, "site", "data"); PL = os.path.join(ROOT, "public-ledger")
canon = lambda o: json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
H = lambda o: hashlib.sha256(canon(o).encode()).hexdigest()
board = json.load(open(os.path.join(S, "leaderboard.json"))); date = board["end"]
FD = os.path.join(S, "fingerprints"); LD = os.path.join(S, "ledger"); os.makedirs(FD, exist_ok=True); os.makedirs(LD, exist_ok=True)
prev = None
for f in sorted(glob.glob(os.path.join(PL, "*", "fingerprints.json"))):
    b = json.load(open(f))
    if b["date"] < date: prev = b
bots, docs = {}, {}
for a in sorted(board["agents"], key=lambda x: x["id"]):
    i = a["id"]; t = json.load(open(os.path.join(S, "trades", i + ".json"))); p = json.load(open(os.path.join(S, "pnl", i + ".json"))); e = json.load(open(os.path.join(S, "equity", i + ".json")))
    doc = {"bot": i, "house": bool(a.get("house")), "retired_at": a.get("retired_at"), "start_date": t["start_date"], "tracked_days": t["tracked_days"], "fill_columns": t["columns"], "fills": t["fills"],
           "daily_columns": e["columns"], "daily": e["daily"], "holdings": p["holdings"], "cash_usd": p["cash_usd"], "total_equity_usd": p["total_equity_usd"],
           "pending_orders": p.get("pending_orders", []), "receipts": p.get("receipts", [])}
    if p.get("timestamped_fills"): doc["timestamped_fills"] = p["timestamped_fills"]   # only present from the first timestamped fill on, so earlier days hash exactly as published
    pv = (prev or {}).get("bots", {}).get(i)
    bots[i] = H({"bot": i, "date": date, "prev": pv, "doc": doc}); docs[i] = {"date": date, "prev": pv, "doc": doc, "sha256": bots[i]}
    json.dump(docs[i], open(os.path.join(LD, i + ".json"), "w"), separators=(",", ":"), ensure_ascii=False)
# Add-only guard: every fill and daily row the previous bundle published (older than 7 days before it) must still be here unchanged.
if prev and os.environ.get("HISTORY_CHANGE_OK") != "1":
    old = json.load(open(os.path.join(PL, prev["date"], "ledgers.json"))); cut = (dt.date.fromisoformat(prev["date"]) - dt.timedelta(days=7)).isoformat(); broken = []
    for i, o in old.items():
        n = docs.get(i)
        if n is None: broken.append(i + ": bot missing"); continue
        for k in ("fills", "daily"):
            a_ = [r for r in o["doc"][k] if r[0] < cut]; b_ = [r for r in n["doc"][k] if r[0] < cut]
            if a_ != b_[:len(a_)] or len(b_) < len(a_): broken.append(f"{i}: {k} history changed")
    if broken:
        raise SystemExit("ABORT: published history would change (" + "; ".join(broken[:5]) + f"; {len(broken)} total). Add a corrections.json entry, then rerun with HISTORY_CHANGE_OK=1.")
prev_root = prev["root"] if prev else None
bundle = {"date": date, "generated_utc": dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "algorithm": "sha256 over canonical JSON (sorted keys, no spaces, UTF-8)",
          "prev_date": prev["date"] if prev else None, "prev_root": prev_root, "bots": bots, "root": H({"date": date, "prev_root": prev_root, "bots": bots}),
          "how_to_verify": "/verify.html", "disclaimer": "Paper trading only. Not financial, investment or legal advice."}
for path in (os.path.join(FD, date + ".json"), os.path.join(FD, "latest.json")): json.dump(bundle, open(path, "w"), indent=1)
od = os.path.join(PL, date); os.makedirs(od, exist_ok=True)
json.dump(bundle, open(os.path.join(od, "fingerprints.json"), "w"), indent=1)
json.dump(docs, open(os.path.join(od, "ledgers.json"), "w"), separators=(",", ":"), ensure_ascii=False)
if not os.path.exists(os.path.join(od, "prices.json")): shutil.copy(os.path.join(S, "prices.json"), os.path.join(od, "prices.json"))   # add-only: a stored day's price file is never overwritten
idx = sorted(os.path.basename(os.path.dirname(f)) for f in glob.glob(os.path.join(PL, "*", "fingerprints.json")))
json.dump({"dates": idx}, open(os.path.join(FD, "index.json"), "w"))
for d in idx:  # every past bundle stays published
    if not os.path.exists(os.path.join(FD, d + ".json")): shutil.copy(os.path.join(PL, d, "fingerprints.json"), os.path.join(FD, d + ".json"))
print("fingerprints:", date, len(bots), "bots, root", bundle["root"][:16], "prev", (prev_root or "none")[:16])
