#!/usr/bin/env node
// Recompute the digest of Tyche's donations ledger yourself.
//
//   node verify.mjs                         # checks the live ledger
//   node verify.mjs ledger.json             # checks a file you saved earlier
//
// The check is deliberately small: canonicalise the body without its own
// digest, hash it, compare. If it matches, the bytes you were served are the
// bytes that were hashed.
//
// It does NOT tell you the ledger is complete. A donation that never reached
// the webhook was never recorded, and no amount of hashing can reveal an
// absence. Treat this as "the published list has not been altered since it was
// published", not as "this is every donation Tyche received".

import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

const SOURCE = "https://tyche.institute/api/ledger";

function canonical(value) {
  if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
  if (value && typeof value === "object") {
    const entries = Object.entries(value)
      .filter(([, v]) => v !== undefined)
      .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
    return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
  }
  return JSON.stringify(value ?? null);
}

const path = process.argv[2];
const text = path ? await readFile(path, "utf8") : await (await fetch(SOURCE)).text();
const parsed = JSON.parse(text);

const { digest_sha256: claimed, ...body } = parsed;
const actual = createHash("sha256").update(canonical(body)).digest("hex");

console.log(`source     ${path ?? SOURCE}`);
console.log(`entries    ${parsed.count}`);
console.log(`total      ${(parsed.total / 100).toFixed(2)} ${String(parsed.entries?.[0]?.currency ?? "eur").toUpperCase()}`);
console.log(`claimed    ${claimed}`);
console.log(`recomputed ${actual}`);

if (claimed === actual) {
  console.log("\nOK — the bytes match the digest published with them.");
  console.log("This says nothing about whether the list is complete.");
} else {
  console.error("\nMISMATCH — these bytes are not what was hashed.");
  process.exit(1);
}
