import { ponder } from "ponder:registry";
import schema from "ponder:schema";
// Helper function to create unique IDs
function createTransferId(event: any) {
  return `${event.transaction.hash}:${event.log.logIndex}`;
}
function createBalanceId(account: string, contract: string, chain: string) {
  return `${account}:${contract}:${chain}`;
}
function createStatsId(contract: string, chain: string) {
  return `${contract}:${chain}`;
}
// Index USDC transfers on Ethereum
ponder.on("UsdcMainnet:Transfer", async ({ event, context }) => {
  const { from, to, value } = event.args;
  const transferId = createTransferId(event);
  
  // Insert transfer record
  await context.db.insert(schema.transfer).values({
    id: transferId,
    from,
    to,
    amount: value,
    contract: event.log.address,
    chain: "ethereum",
    blockNumber: event.block.number,
    timestamp: Number(event.block.timestamp),
  });
  // Update balances for both accounts (if not mint/burn)
  const zeroAddress = "0x0000000000000000000000000000000000000000";
  
  if (from !== zeroAddress) {
    await updateBalance(context, from, event.log.address, "ethereum", -value, event.block.timestamp);
  }
  
  if (to !== zeroAddress) {
    await updateBalance(context, to, event.log.address, "ethereum", value, event.block.timestamp);
  }
  // Update token statistics
  await updateTokenStats(context, event.log.address, "ethereum", value, event.block.timestamp);
});
// Index USDC transfers on Polygon
ponder.on("UsdcPolygon:Transfer", async ({ event, context }) => {
  const { from, to, value } = event.args;
  const transferId = createTransferId(event);
  
  await context.db.insert(schema.transfer).values({
    id: transferId,
    from,
    to,
    amount: value,
    contract: event.log.address,
    chain: "polygon",
    blockNumber: event.block.number,
    timestamp: Number(event.block.timestamp),
  });
  const zeroAddress = "0x0000000000000000000000000000000000000000";
  
  if (from !== zeroAddress) {
    await updateBalance(context, from, event.log.address, "polygon", -value, event.block.timestamp);
  }
  
  if (to !== zeroAddress) {
    await updateBalance(context, to, event.log.address, "polygon", value, event.block.timestamp);
  }
  await updateTokenStats(context, event.log.address, "polygon", value, event.block.timestamp);
});
// Index USDC transfers on Base
ponder.on("UsdcBase:Transfer", async ({ event, context }) => {
  const { from, to, value } = event.args;
  const transferId = createTransferId(event);
  
  await context.db.insert(schema.transfer).values({
    id: transferId,
    from,
    to,
    amount: value,
    contract: event.log.address,
    chain: "base", 
    blockNumber: event.block.number,
    timestamp: Number(event.block.timestamp),
  });
  const zeroAddress = "0x0000000000000000000000000000000000000000";
  
  if (from !== zeroAddress) {
    await updateBalance(context, from, event.log.address, "base", -value, event.block.timestamp);
  }
  
  if (to !== zeroAddress) {
    await updateBalance(context, to, event.log.address, "base", value, event.block.timestamp);
  }
  await updateTokenStats(context, event.log.address, "base", value, event.block.timestamp);
});
// Helper function to update account balances
async function updateBalance(
  context: any,
  account: string, 
  contract: string,
  chain: string,
  change: bigint,
  blockTimestamp: bigint
) {
  const balanceId = createBalanceId(account, contract, chain);
  
  // Try to get existing balance
  const existingBalance = await context.db.find(schema.balance, {
    id: balanceId
  });
  if (existingBalance) {
    // Update existing balance
    const oldBalance = existingBalance.balance;
    const newBalance = oldBalance + change;
    
    // Handle cases where we're processing historical data from a start block
    // and don't have the complete balance history
    if (newBalance < 0n && change < 0n) {
      // This suggests the account had a balance before our start block
      // Initialize with the absolute value of the change (minimum needed balance)
      const initializedBalance = -change;
      await context.db.update(schema.balance, {
        id: balanceId
      }).set({
        balance: initializedBalance + change, // This will equal initializedBalance - abs(change) = 0
        lastUpdated: Number(blockTimestamp),
      });
      return; // Exit early after handling this case
    } else if (newBalance < 0n) {
      throw new Error(`Balance would become negative for account ${account}. Old: ${oldBalance}, Change: ${change}`);
    }
    
    await context.db.update(schema.balance, {
      id: balanceId
    }).set({
      balance: newBalance,
      lastUpdated: Number(blockTimestamp),
    });
    // Update holder count based on balance changes
    if (oldBalance === 0n && newBalance > 0n) {
      // New holder (balance went from 0 to positive)
      await updateHolderCount(context, contract, chain, 1, blockTimestamp);
    } else if (oldBalance > 0n && newBalance === 0n) {
      // Holder exit (balance went from positive to 0)
      await updateHolderCount(context, contract, chain, -1, blockTimestamp);
    }
  } else {
    // Create new balance record
    if (change < 0n) {
      // Account is sending tokens but has no balance record
      // This means they had tokens before our start block
      // Initialize with the absolute value of the change, then subtract it
      const initializedBalance = -change;
      await context.db.insert(schema.balance).values({
        id: balanceId,
        account,
        contract,
        chain,
        balance: 0n, // After the transfer, balance will be 0
        lastUpdated: Number(blockTimestamp),
      });
      // Account just exited its position – adjust holder count
      await updateHolderCount(context, contract, chain, -1, blockTimestamp);
    } else {
      // Normal case: receiving tokens to a new account
      await context.db.insert(schema.balance).values({
        id: balanceId,
        account,
        contract,
        chain,
        balance: change,
        lastUpdated: Number(blockTimestamp),
      });
      // If this is a new holder with positive balance, increment holder count
      if (change > 0n) {
        await updateHolderCount(context, contract, chain, 1, blockTimestamp);
      }
    }
  }
}
// Helper function to update holder count
async function updateHolderCount(
  context: any,
  contract: string,
  chain: string,
  change: number,
  blockTimestamp: bigint
) {
  const statsId = createStatsId(contract, chain);
  
  const existingStats = await context.db.find(schema.tokenStats, {
    id: statsId
  });
  if (existingStats) {
    await context.db.update(schema.tokenStats, {
      id: statsId
    }).set({
      totalHolders: Math.max(0, existingStats.totalHolders + change),
      lastUpdated: Number(blockTimestamp),
    });
  } else {
    // Create new token stats record for the first holder event
    await context.db.insert(schema.tokenStats).values({
      id: statsId,
      contract,
      chain,
      totalHolders: Math.max(0, change),
      totalTransfers: 0n,
      totalVolume: 0n,
      lastUpdated: Number(blockTimestamp),
    });
  }
}
// Helper function to update token statistics
async function updateTokenStats(
  context: any,
  contract: string,
  chain: string, 
  volume: bigint,
  blockTimestamp: bigint
) {
  const statsId = createStatsId(contract, chain);
  
  const existingStats = await context.db.find(schema.tokenStats, {
    id: statsId
  });
  if (existingStats) {
    await context.db.update(schema.tokenStats, {
      id: statsId
    }).set({
      totalTransfers: existingStats.totalTransfers + 1n,
      totalVolume: existingStats.totalVolume + volume,
      lastUpdated: Number(blockTimestamp),
    });
  } else {
    await context.db.insert(schema.tokenStats).values({
      id: statsId,
      contract,
      chain,
      totalHolders: 0,
      totalTransfers: 1n,
      totalVolume: volume,
      lastUpdated: Number(blockTimestamp),
    });
  }
}