import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';

const here = new URL('.', import.meta.url);
const readJson = async (name) => JSON.parse(await readFile(new URL(name, here), 'utf8'));
const [btc, cardano, exchequer, stable, sources] = await Promise.all([
  readJson('btc-origins-flow.json'),
  readJson('cardano-origins-flow.json'),
  readJson('exchequer-shared-terminal-flow.json'),
  readJson('bos-proceeds-509-trace.json'),
  readJson('sources.json')
]);

const SCALE = 24n;
const TEN = 10n;
const decimal = (value) => {
  const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(String(value));
  assert.ok(match, `invalid decimal ${value}`);
  const sign = match[1] === '-' ? -1n : 1n;
  const fraction = (match[3] || '').padEnd(Number(SCALE), '0').slice(0, Number(SCALE));
  return sign * (BigInt(match[2]) * TEN ** SCALE + BigInt(fraction));
};
const fixed = (value, places = 6) => {
  const negative = value < 0n;
  const absolute = negative ? -value : value;
  const divisor = TEN ** (SCALE - BigInt(places));
  const rounded = (absolute + divisor / 2n) / divisor;
  const raw = rounded.toString().padStart(places + 1, '0');
  const whole = raw.slice(0, -places) || '0';
  const fraction = raw.slice(-places);
  return `${negative ? '-' : ''}${whole}${places ? `.${fraction}` : ''}`;
};
const sum = (values) => values.reduce((total, value) => total + decimal(value), 0n);
const percentFixed = (numerator, denominator, places = 6) => {
  const factor = TEN ** BigInt(places);
  const rounded = (numerator * 100n * factor + denominator / 2n) / denominator;
  const raw = rounded.toString().padStart(places + 1, '0');
  return `${raw.slice(0, -places) || '0'}${places ? `.${raw.slice(-places)}` : ''}`;
};

const registeredSourceIds = new Set(sources.map((source) => source.id));
assert.equal(registeredSourceIds.size, sources.length, 'sources.json contains duplicate ids');
const verifySourceReferences = (value, path) => {
  if (Array.isArray(value)) {
    value.forEach((entry, index) => verifySourceReferences(entry, `${path}[${index}]`));
    return;
  }
  if (!value || typeof value !== 'object') return;
  for (const [key, entry] of Object.entries(value)) {
    if (key === 'sourceIds') {
      assert.ok(Array.isArray(entry), `${path}.sourceIds must be an array`);
      for (const id of entry) assert.ok(registeredSourceIds.has(id), `${path}.sourceIds contains unresolved id ${id}`);
    } else {
      verifySourceReferences(entry, `${path}.${key}`);
    }
  }
};
verifySourceReferences(btc, 'btc-origins-flow.json');
verifySourceReferences(cardano, 'cardano-origins-flow.json');
verifySourceReferences(exchequer, 'exchequer-shared-terminal-flow.json');
verifySourceReferences(stable, 'bos-proceeds-509-trace.json');

assert.equal(btc.collectorTopology.window.closedUTC, '2025-10-22T09:00:00Z');
assert.equal(
  decimal(btc.collectorTopology.grossExternalInputsBTC) - decimal(btc.collectorTopology.sweepFeesBTC),
  decimal(btc.collectorTopology.netAtCollectorsBTC)
);
assert.equal(
  sum(btc.candidateBosToTerminal.events.map((event) => event.amountBTC)),
  decimal(btc.candidateBosToTerminal.grossDepositsBTC)
);
assert.equal(
  decimal('18.64022053') - decimal('0.08635056') + decimal('0.00305709') + decimal('1.45437051') - decimal(btc.candidateBosToTerminal.downstreamFeesBTC),
  decimal(btc.candidateBosToTerminal.strictSaleWindowLowerBoundBTC)
);
assert.equal(
  sum(btc.exchequerToTerminal.events.map((event) => event.amountBTC)),
  decimal(btc.exchequerToTerminal.terminalReceivedBTC)
);
assert.equal(new Set(btc.candidateBosToTerminal.events.map((event) => event.tx)).size, 8);

assert.equal(sum(cardano.cohorts.map((cohort) => cohort.amountADA)), decimal(cardano.totals.amountADA));
assert.equal(cardano.cohorts.reduce((total, cohort) => total + cohort.depositUtxos, 0), cardano.totals.depositUtxos);
assert.equal(cardano.cohorts.reduce((total, cohort) => total + cohort.uniqueDepositAddresses, 0), cardano.totals.uniqueDepositAddresses);
assert.equal(sum(cardano.binanceRoute.pairs.map((pair) => pair.grossADA)), decimal(cardano.binanceRoute.grossADA));
assert.equal(sum(cardano.binanceRoute.pairs.map((pair) => pair.netADA)), decimal(cardano.binanceRoute.netADA));
assert.equal(decimal(cardano.binanceRoute.grossADA) - decimal(cardano.binanceRoute.netADA), decimal(cardano.binanceRoute.relayFeesADA));
assert.equal(
  decimal(cardano.cohorts[2].amountADA)
    - decimal(cardano.postCutoverUses.collectorSweepFeesADA)
    - decimal(cardano.postCutoverUses.collectorOutflowFeesADA)
    - decimal(cardano.postCutoverUses.distributorBranch.collectorOutflowADA)
    - decimal(cardano.postCutoverUses.liquidityBranch.collectorToControllerADA),
  0n
);
assert.equal(
  decimal(cardano.postCutoverUses.liquidityBranch.orderADA)
    - decimal(cardano.postCutoverUses.liquidityBranch.poolReserveIncreaseADA),
  decimal('2.75')
);
assert.equal(
  decimal(cardano.postCutoverUses.liquidityBranch.orderBOS),
  decimal(cardano.postCutoverUses.liquidityBranch.poolReserveIncreaseBOS)
);

assert.equal(sum(exchequer.ddSourceReconciliation.components.map((component) => component.amountUSDT)), decimal(exchequer.ddSourceReconciliation.totalExchequerOriginUSDT));
assert.equal(
  decimal(exchequer.ddSourceReconciliation.completeCanonicalLedger.totalInflowsUSDT)
    - decimal(exchequer.ddSourceReconciliation.completeCanonicalLedger.totalOutflowsUSDT),
  decimal(exchequer.ddSourceReconciliation.completeCanonicalLedger.cutoffBalanceUSDT)
);
assert.equal(
  decimal(exchequer.januaryExchequerRoute.grossUSDT) - decimal(exchequer.januaryExchequerRoute.maximumOtherDdFundsUSDT),
  decimal(exchequer.januaryExchequerRoute.standaloneExchequerLowerBoundUSDT)
);
assert.equal(
  decimal(exchequer.marchExchequerRoute.grossDdTo509USDT)
    - decimal(exchequer.marchExchequerRoute.maximumOtherDdFundsUSDT)
    - decimal(exchequer.marchExchequerRoute.preexisting509USDT)
    - decimal(exchequer.marchExchequerRoute.curveShortfall)
    - decimal(exchequer.marchExchequerRoute.postSend509ResidualUSDC),
  decimal(exchequer.marchExchequerRoute.standaloneExchequerEndpointLowerBound)
);
assert.equal(
  decimal(exchequer.jointTerminalBound.januaryAndMarchGrossDDOutflows)
    - decimal(exchequer.jointTerminalBound.maximumOtherDdFundsAllocatedOnce)
    - decimal(exchequer.jointTerminalBound.maximumMarchDownstreamDilution),
  decimal(exchequer.jointTerminalBound.strictExchequerLowerBoundAtTerminal)
);
assert.equal(
  decimal(exchequer.recurringRecipientNetwork.ddGrossUSDT) - decimal(exchequer.recurringRecipientNetwork.maximumOtherDdFundsUSDT),
  decimal(exchequer.recurringRecipientNetwork.strictExchequerLowerBoundUSDT)
);
assert.equal(
  decimal(exchequer.ddSourceReconciliation.completeCanonicalLedger.totalOutflowsUSDT)
    - decimal(exchequer.ddSourceReconciliation.completeCanonicalLedger.maximumOtherFundsBeforeFinalSubstantiveOutflowUSDT)
    - decimal(exchequer.jointTerminalBound.maximumMarchDownstreamDilution),
  decimal(exchequer.combinedDestinationBound.strictExchequerLowerBoundToRecurringRecipientsOrCommonTerminal)
);

const benchmark = stable.multichainPublicBenchmark;
const stablecoin = benchmark.stablecoinBenchmark;
const bnbRoots = stablecoin.additionalNonOverlappingBnbExternalRoots;
assert.equal(
  sum(bnbRoots.collectors.map((collector) => collector.USDT)),
  decimal(bnbRoots.totalUSDT)
);
assert.equal(
  sum(bnbRoots.collectors.map((collector) => collector.USDC)),
  decimal(bnbRoots.totalUSDC)
);
for (const collector of bnbRoots.collectors) {
  assert.equal(decimal(collector.USDT) + decimal(collector.USDC), decimal(collector.stablecoinFaceValueUSD));
}
assert.equal(
  sum(bnbRoots.collectors.map((collector) => collector.stablecoinFaceValueUSD)),
  decimal(bnbRoots.totalStablecoinFaceValueUSD)
);
assert.equal(
  decimal(stable.originsCounterReconciliation.expandedCanonicalStablecoinReceipts),
  decimal(stablecoin.expandedEvmAndPolygonCanonicalReceiptsUSD)
);
const stableUSD = decimal(stablecoin.expandedEvmAndPolygonCanonicalReceiptsUSD)
  + decimal(bnbRoots.totalStablecoinFaceValueUSD);
assert.equal(stableUSD, decimal(stablecoin.combinedCanonicalStablecoinReceiptsUSD));
assert.equal(
  percentFixed(stableUSD, decimal(benchmark.combinedBenchmark.originsCounterUSD)),
  stablecoin.originsCounterCoveragePercent
);
assert.equal(decimal(benchmark.volatileAssetBenchmarks.cardanoRootsADA), decimal(cardano.totals.amountADA));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.cardanoLowUSD), decimal(cardano.benchmark.lowUSD));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.cardanoVwapUSD), decimal(cardano.benchmark.vwapUSD));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.cardanoHighUSD), decimal(cardano.benchmark.highUSD));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.bitcoinCleanRootsBTC), decimal(btc.benchmark.cleanGrossBTC));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.bitcoinLowUSD), decimal(btc.benchmark.lowUSD));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.bitcoinVwapUSD), decimal(btc.benchmark.vwapUSD));
assert.equal(decimal(benchmark.volatileAssetBenchmarks.bitcoinHighUSD), decimal(btc.benchmark.highUSD));
const centralUSD = stableUSD + decimal(cardano.benchmark.vwapUSD) + decimal(btc.benchmark.vwapUSD);
const lowUSD = stableUSD + decimal(cardano.benchmark.lowUSD) + decimal(btc.benchmark.lowUSD);
const highUSD = stableUSD + decimal(cardano.benchmark.highUSD) + decimal(btc.benchmark.highUSD);
assert.equal(lowUSD, decimal(benchmark.combinedBenchmark.lowUSD));
assert.equal(centralUSD, decimal(benchmark.combinedBenchmark.vwapUSD));
assert.equal(highUSD, decimal(benchmark.combinedBenchmark.highUSD));
assert.equal(
  decimal(benchmark.combinedBenchmark.originsCounterUSD) - centralUSD,
  decimal(benchmark.combinedBenchmark.remainingVwapDifferenceUSD)
);
const coverage = percentFixed(centralUSD, decimal(benchmark.combinedBenchmark.originsCounterUSD));
assert.equal(coverage, benchmark.combinedBenchmark.vwapCoveragePercent);

process.stdout.write([
  'Fund-flow verification PASS',
  `BOS BTC clean topology: ${btc.collectorTopology.grossExternalInputsBTC} BTC`,
  `Common BTC terminal: BOS lower bound ${btc.candidateBosToTerminal.strictSaleWindowLowerBoundBTC} BTC; Exchequer ${btc.exchequerToTerminal.terminalReceivedBTC} BTC`,
  `Cardano Origins-style roots: ${cardano.totals.amountADA} ADA`,
  `Exchequer minimum to recurring recipients or common Ethereum terminal: ${exchequer.combinedDestinationBound.strictExchequerLowerBoundToRecurringRecipientsOrCommonTerminal}`,
  `Central reconstructed benchmark: $${fixed(centralUSD, 2)} (${Number(coverage).toFixed(2)}% of Origins counter)`
].join('\n') + '\n');
