import { ponder } from 'ponder:registry';
import { getLogger } from './helpers/logger';
import { stats, holders, transactions, STATS_ID } from 'ponder:schema';
import {
  ensureStatsExists,
  parseRingBuffer,
  serializeRingBuffer,
  updateHourlyData,
  checkBlockHistorySufficient,
  RING_BUFFER_SEGMENTS,
  refreshMinStake,
  updateEthReserve,
} from './helpers/stats';
import { validateContractVersion } from './helpers/version';

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;

// Pool address for detecting swaps (buys/sells)
// Computed deterministically from Uniswap V3 factory + WETH + Kraiken + 1% fee
const POOL_ADDRESS = (process.env.POOL_ADDRESS || '0x1f69cbfc7d3529a4fb4eadf18ec5644b2603b5ab').toLowerCase() as `0x${string}`;

// Track if version has been validated
let versionValidated = false;

ponder.on('Kraiken:Transfer', async ({ event, context }) => {
  // Validate version once at first event
  if (!versionValidated) {
    await validateContractVersion(context);
    versionValidated = true;
  }

  const { from, to, value } = event.args;

  await ensureStatsExists(context, event.block.timestamp);

  // Track holder balances for ALL transfers (not just mint/burn)
  const statsData = await context.db.find(stats, { id: STATS_ID });
  if (!statsData) return;

  let holderCountDelta = 0;

  // CRITICAL FIX: Skip holder tracking for self-transfers (from === to)
  // Self-transfers don't change balances or holder counts
  const isSelfTransfer = from !== ZERO_ADDRESS && to !== ZERO_ADDRESS && from === to;

  if (!isSelfTransfer) {
    // Update 'from' holder (if not mint)
    if (from !== ZERO_ADDRESS) {
      const fromHolder = await context.db.find(holders, { address: from });

      // CRITICAL FIX: Validate that holder exists before processing transfer
      if (!fromHolder) {
        getLogger(context).error(`Transfer from non-existent holder ${from} in block ${event.block.number}. This should not happen.`);
        // Don't process this transfer's holder tracking
        return;
      }

      const newBalance = fromHolder.balance - value;

      // CRITICAL FIX: Prevent negative balances
      if (newBalance < 0n) {
        getLogger(context).error(`Transfer would create negative balance for ${from}: ${fromHolder.balance} - ${value} = ${newBalance}`);
        return;
      }

      if (newBalance === 0n) {
        // Holder balance went to zero - remove from holder count
        await context.db.update(holders, { address: from }).set({
          balance: 0n,
        });
        holderCountDelta -= 1;
      } else {
        await context.db.update(holders, { address: from }).set({
          balance: newBalance,
        });
      }
    }

    // Update 'to' holder (if not burn)
    if (to !== ZERO_ADDRESS) {
      const toHolder = await context.db.find(holders, { address: to });
      const oldBalance = toHolder?.balance ?? 0n;
      const newBalance = oldBalance + value;

      // Detect buy: tokens coming FROM the pool = user bought KRK
      const isBuy = from.toLowerCase() === POOL_ADDRESS;
      let ethSpentDelta = 0n;

      if (isBuy && value > 0n) {
        // Approximate ETH cost using current price from stats
        const currentPrice = statsData.currentPriceWei ?? 0n;
        if (currentPrice > 0n) {
          ethSpentDelta = (value * currentPrice) / 10n ** 18n;
        }
      }

      if (toHolder) {
        await context.db.update(holders, { address: to }).set({
          balance: newBalance,
          ...(isBuy && {
            totalEthSpent: (toHolder.totalEthSpent ?? 0n) + ethSpentDelta,
            totalTokensAcquired: (toHolder.totalTokensAcquired ?? 0n) + value,
          }),
        });
        // If this was a new holder (balance was 0), increment count
        if (oldBalance === 0n) {
          holderCountDelta += 1;
        }
      } else {
        // New holder
        await context.db.insert(holders).values({
          address: to,
          balance: newBalance,
          totalEthSpent: ethSpentDelta,
          totalTokensAcquired: isBuy ? value : 0n,
        });
        holderCountDelta += 1;
      }
    }
  }

  // Record buy/sell transactions
  if (!isSelfTransfer && from !== ZERO_ADDRESS && to !== ZERO_ADDRESS) {
    const isBuy = from.toLowerCase() === POOL_ADDRESS;
    const isSell = to.toLowerCase() === POOL_ADDRESS;

    if (isBuy || isSell) {
      const currentPrice = statsData.currentPriceWei ?? 0n;
      const ethEstimate = currentPrice > 0n ? (value * currentPrice) / 10n ** 18n : 0n;
      const txId = `${event.transaction.hash}-${event.log.logIndex}`;

      await context.db.insert(transactions).values({
        id: txId,
        holder: isBuy ? to : from,
        type: isBuy ? 'buy' : 'sell',
        tokenAmount: value,
        ethAmount: ethEstimate,
        timestamp: event.block.timestamp,
        blockNumber: Number(event.block.number),
        txHash: event.transaction.hash,
      });

      // Update ETH reserve from pool WETH balance — buys/sells shift pool ETH
      await updateEthReserve(context, POOL_ADDRESS);
    }
  }

  // Update holder count if changed (with underflow protection)
  if (holderCountDelta !== 0) {
    const newHolderCount = statsData.holderCount + holderCountDelta;

    // IMPORTANT FIX: Prevent holder count underflow
    if (newHolderCount < 0) {
      getLogger(context).error(
        `Holder count would go negative: ${statsData.holderCount} + ${holderCountDelta} = ${newHolderCount}. Skipping update.`
      );
      return;
    }

    await context.db.update(stats, { id: STATS_ID }).set({
      holderCount: newHolderCount,
    });
  }

  // Check if we have sufficient block history for reliable ringbuffer operations
  if (!checkBlockHistorySufficient(context, event)) {
    // Insufficient history - skip ringbuffer updates but continue with basic stats
    if (from === ZERO_ADDRESS) {
      await context.db.update(stats, { id: STATS_ID }).set({
        kraikenTotalSupply: statsData.kraikenTotalSupply + value,
        totalMinted: statsData.totalMinted + value,
      });
    } else if (to === ZERO_ADDRESS) {
      await context.db.update(stats, { id: STATS_ID }).set({
        kraikenTotalSupply: statsData.kraikenTotalSupply - value,
        totalBurned: statsData.totalBurned + value,
      });
    }
    return;
  }

  await updateHourlyData(context, event.block.timestamp);

  const ringBuffer = parseRingBuffer(statsData.ringBuffer as string[]);
  const pointer = statsData.ringBufferPointer ?? 0;
  const baseIndex = pointer * RING_BUFFER_SEGMENTS;

  if (from === ZERO_ADDRESS) {
    ringBuffer[baseIndex + 1] = ringBuffer[baseIndex + 1] + value;

    await context.db.update(stats, { id: STATS_ID }).set({
      ringBuffer: serializeRingBuffer(ringBuffer),
      kraikenTotalSupply: statsData.kraikenTotalSupply + value,
      totalMinted: statsData.totalMinted + value,
    });
  } else if (to === ZERO_ADDRESS) {
    ringBuffer[baseIndex + 2] = ringBuffer[baseIndex + 2] + value;

    await context.db.update(stats, { id: STATS_ID }).set({
      ringBuffer: serializeRingBuffer(ringBuffer),
      kraikenTotalSupply: statsData.kraikenTotalSupply - value,
      totalBurned: statsData.totalBurned + value,
    });
  }

  await updateHourlyData(context, event.block.timestamp);
});

ponder.on('StatsBlock:block', async ({ event, context }) => {
  const statsData = await ensureStatsExists(context, event.block.timestamp);

  await refreshMinStake(context, statsData);

  // Only update hourly data if we have sufficient block history
  if (checkBlockHistorySufficient(context, event)) {
    await updateHourlyData(context, event.block.timestamp);
  }
});
