#!/usr/bin/env npx tsx
/**
 * distribute-publisher-trac.ts
 *
 * Distributes TRAC from a distribution wallet to publishers based on the
 * aggregate files generated by generate-aggregates.ts.
 *
 * Guards against double-distribution:
 *   - Maintains a ledger file (<chain>_distribution_ledger.json) tracking
 *     every successful transfer with tx hash and timestamp.
 *   - Before each transfer, checks the ledger — if the publisher was already
 *     paid the exact amount, it's skipped.
 *   - Safe to re-run: completed transfers are skipped, partial runs resume.
 *
 * Prerequisites:
 *   - Distribution wallet funded with TRAC (pulled from StakingStorage manually)
 *   - Private key in DISTRIBUTION_WALLET_KEY env var (or .env file)
 *
 * Usage:
 *   npx tsx scripts/distribute-publisher-trac.ts <chain> [--dry-run]
 *
 * Examples:
 *   npx tsx scripts/distribute-publisher-trac.ts base --dry-run
 *   npx tsx scripts/distribute-publisher-trac.ts neuroweb
 */
import { ethers, JsonRpcProvider, Wallet, Contract } from 'ethers';
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import path from 'node:path';

// ── Load .env ───────────────────────────────────────────────────────────

function loadEnv() {
  const envPath = path.join(__dirname, '..', '.env');
  if (!existsSync(envPath)) return;
  for (const line of readFileSync(envPath, 'utf8').split('\n')) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
    const eqIdx = trimmed.indexOf('=');
    if (eqIdx < 0) continue;
    const key = trimmed.slice(0, eqIdx).trim();
    const val = trimmed.slice(eqIdx + 1).trim();
    if (!process.env[key]) process.env[key] = val;
  }
}

loadEnv();

// ── Config ──────────────────────────────────────────────────────────────

const ERC20_ABI = [
  'function transfer(address to, uint256 amount) returns (bool)',
  'function balanceOf(address) view returns (uint256)',
  'function symbol() view returns (string)',
];

const TOKEN_ADDRESSES: Record<string, string> = {
  base: '0xA81a52B4dda010896cDd386C7fBdc5CDc835ba23',
  gnosis: '0xEddd81E0792E764501AaE206EB432399a0268DB5',
  neuroweb: '0xFfFFFFff00000000000000000000000000000001',
};

const RPC_ENV_KEYS: Record<string, string> = {
  base: 'RPC_BASE_MAINNET',
  gnosis: 'RPC_GNOSIS_MAINNET',
  neuroweb: 'RPC_NEUROWEB_MAINNET',
};

// ── Types ───────────────────────────────────────────────────────────────

interface DistributionEntry {
  address: string;
  totalWei: string;
  totalTRAC: number;
}

interface DistributionFile {
  chain: string;
  count: number;
  totalWei: string;
  totalTRAC: number;
  publishers: DistributionEntry[];
}

interface LedgerEntry {
  address: string;
  amountWei: string;
  txHash: string;
  timestamp: string;
  blockNumber: number;
}

interface Ledger {
  chain: string;
  distributionWallet: string;
  startedAt: string;
  entries: LedgerEntry[];
}

// ── Helpers ─────────────────────────────────────────────────────────────

function loadLedger(ledgerPath: string, chain: string, wallet: string): Ledger {
  if (existsSync(ledgerPath)) {
    return JSON.parse(readFileSync(ledgerPath, 'utf8'));
  }
  return {
    chain,
    distributionWallet: wallet,
    startedAt: new Date().toISOString(),
    entries: [],
  };
}

function saveLedger(ledgerPath: string, ledger: Ledger) {
  writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2) + '\n');
}

function fmt(n: number): string {
  return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

// ── Main ────────────────────────────────────────────────────────────────

async function main() {
  const args = process.argv.slice(2);
  const chain = args.find(a => !a.startsWith('--'));
  const dryRun = args.includes('--dry-run');

  if (!chain || !TOKEN_ADDRESSES[chain]) {
    console.error(`Usage: npx tsx scripts/distribute-publisher-trac.ts <chain> [--dry-run]`);
    console.error(`Chains: ${Object.keys(TOKEN_ADDRESSES).join(', ')}`);
    process.exit(1);
  }

  if (dryRun) {
    console.log('🔍 DRY RUN — no transactions will be sent\n');
  }

  // Load distribution file
  const distPath = path.join(__dirname, '..', 'snapshots', `${chain}_publisher_distribution.json`);
  if (!existsSync(distPath)) {
    console.error(`Distribution file not found: ${distPath}`);
    console.error('Run generate-aggregates.ts first.');
    process.exit(1);
  }
  const distData: DistributionFile = JSON.parse(readFileSync(distPath, 'utf8'));

  // Connect to RPC
  const rpcUrl = (process.env[RPC_ENV_KEYS[chain]] ?? '').split(',')[0];
  if (!rpcUrl) {
    console.error(`No RPC configured for ${chain}. Set ${RPC_ENV_KEYS[chain]} in .env`);
    process.exit(1);
  }
  const provider = new JsonRpcProvider(rpcUrl);

  // Load distribution wallet
  const privateKey = process.env.DISTRIBUTION_WALLET_KEY;
  if (!privateKey) {
    console.error('Set DISTRIBUTION_WALLET_KEY in .env or environment. Refusing to use an ephemeral wallet, even for dry runs.');
    process.exit(1);
  }

  const wallet = new Wallet(privateKey, provider);

  const walletAddress = await wallet.getAddress();
  const token = new Contract(TOKEN_ADDRESSES[chain], ERC20_ABI, wallet);

  // Check wallet TRAC balance
  const walletBalance: bigint = await token.balanceOf(walletAddress);
  const totalNeeded = BigInt(distData.totalWei);

  console.log(`Chain:              ${chain}`);
  console.log(`Distribution wallet: ${walletAddress}`);
  console.log(`TRAC balance:        ${ethers.formatEther(walletBalance)} TRAC`);
  console.log(`Total to distribute: ${ethers.formatEther(totalNeeded)} TRAC`);
  console.log(`Publishers:          ${distData.count}`);
  console.log('');

  // Load ledger (tracks completed transfers)
  const ledgerPath = path.join(__dirname, '..', 'snapshots', `${chain}_distribution_ledger.json`);
  const ledger = loadLedger(ledgerPath, chain, walletAddress);
  const completedSet = new Map<string, string>();
  for (const entry of ledger.entries) {
    completedSet.set(`${entry.address}:${entry.amountWei}`, entry.txHash);
  }

  // Calculate how much is already distributed
  let alreadyDistributed = 0n;
  let skippedCount = 0;
  for (const pub of distData.publishers) {
    const key = `${pub.address}:${pub.totalWei}`;
    if (completedSet.has(key)) {
      alreadyDistributed += BigInt(pub.totalWei);
      skippedCount++;
    }
  }

  const remaining = totalNeeded - alreadyDistributed;
  const remainingCount = distData.count - skippedCount;

  if (skippedCount > 0) {
    console.log(`Already distributed: ${ethers.formatEther(alreadyDistributed)} TRAC (${skippedCount} publishers)`);
    console.log(`Remaining:           ${ethers.formatEther(remaining)} TRAC (${remainingCount} publishers)`);
    console.log('');
  }

  if (remainingCount === 0) {
    console.log('All publishers already distributed. Nothing to do.');
    return;
  }

  if (!dryRun && walletBalance < remaining) {
    console.error(`Insufficient balance: have ${ethers.formatEther(walletBalance)}, need ${ethers.formatEther(remaining)}`);
    process.exit(1);
  }

  // Process each publisher
  let successCount = 0;
  let failCount = 0;
  let distributedWei = 0n;

  for (let i = 0; i < distData.publishers.length; i++) {
    const pub = distData.publishers[i];
    const key = `${pub.address}:${pub.totalWei}`;

    // Double-send guard
    if (completedSet.has(key)) {
      continue;
    }

    const idx = skippedCount + successCount + failCount + 1;
    const amountStr = ethers.formatEther(BigInt(pub.totalWei));
    process.stdout.write(`  [${idx}/${remainingCount}] ${pub.address}  ${fmt(pub.totalTRAC)} TRAC ... `);

    if (dryRun) {
      console.log('SKIP (dry run)');
      distributedWei += BigInt(pub.totalWei);
      successCount++;
      continue;
    }

    try {
      const tx = await token.transfer(pub.address, BigInt(pub.totalWei));
      const receipt = await tx.wait();

      ledger.entries.push({
        address: pub.address,
        amountWei: pub.totalWei,
        txHash: receipt.hash,
        timestamp: new Date().toISOString(),
        blockNumber: receipt.blockNumber,
      });
      completedSet.set(key, receipt.hash);
      saveLedger(ledgerPath, ledger);

      distributedWei += BigInt(pub.totalWei);
      successCount++;
      console.log(`OK (tx: ${receipt.hash})`);
    } catch (err: any) {
      failCount++;
      console.log(`FAILED: ${err.message?.substring(0, 100)}`);

      if (failCount >= 3) {
        console.error('\n3 consecutive failures — stopping. Re-run to resume from where we left off.');
        break;
      }
    }
  }

  // Summary
  console.log(`\n${'='.repeat(60)}`);
  console.log(`Distribution summary for ${chain}:`);
  console.log(`  Successful: ${successCount}`);
  if (failCount > 0) console.log(`  Failed:     ${failCount}`);
  console.log(`  Skipped:    ${skippedCount} (already completed)`);
  console.log(`  Distributed: ${ethers.formatEther(distributedWei)} TRAC`);

  if (!dryRun) {
    const finalBalance: bigint = await token.balanceOf(walletAddress);
    console.log(`  Wallet balance remaining: ${ethers.formatEther(finalBalance)} TRAC`);
  }

  console.log(`  Ledger: ${ledgerPath}`);
}

main().catch((err) => {
  console.error('Fatal:', err);
  process.exit(1);
});
