import { ponder } from 'ponder:registry';
import { getLogger } from './helpers/logger';
import { recenters, stats, STATS_ID, HOURS_IN_RING_BUFFER } from 'ponder:schema';
import { ensureStatsExists, recordEthReserveSnapshot, parseRingBuffer, RING_BUFFER_SEGMENTS } from './helpers/stats';

/**
 * Fee tracking approach:
 *
 * Option 1 (not implemented): Index Uniswap V3 Pool Collect events
 * - Pros: Accurate fee data directly from the pool
 * - Cons: Requires adding pool contract to ponder.config.ts, forcing a full resync
 *
 * Option 2 (not implemented): Derive from ETH balance changes
 * - Pros: No config changes needed
 * - Cons: Less accurate, hard to isolate fees from other balance changes
 *
 * The feesEarned7dEth and feesEarned7dKrk fields default to 0n until implemented.
 */

/**
 * Calculate price in wei per KRK token from a Uniswap V3 tick
 * For WETH/KRK pool where WETH is token0:
 * - price = amount1/amount0 = 1.0001^tick
 * - This gives KRK per WETH
 * - We want wei per KRK, so we invert and scale
 */
function priceFromTick(tick: number): bigint {
  // Calculate 1.0001^tick using floating point
  const price = Math.pow(1.0001, tick);

  // Price is KRK/WETH, we want WEI per KRK
  // Since both tokens have 18 decimals, we need to invert
  // priceWei = (10^18) / price
  const priceWei = 10 ** 18 / price;

  return BigInt(Math.floor(priceWei));
}

/**
 * Calculate basis points difference between two values
 * bps = (new - old) / old * 10000
 */
function calculateBps(newValue: bigint, oldValue: bigint): number {
  if (oldValue === 0n) return 0;
  const diff = newValue - oldValue;
  const bps = (Number(diff) * 10000) / Number(oldValue);
  return Math.floor(bps);
}

/**
 * Handle LiquidityManager Recentered events
 * Creates a new recenter record and updates stats.
 * NOTE: Recenter day/week counts are simple incrementing counters.
 * For accurate rolling windows, the API layer can query the recenters table directly.
 */
ponder.on('LiquidityManager:Recentered', async ({ event, context }) => {
  await ensureStatsExists(context, event.block.timestamp);

  const { currentTick, isUp } = event.args;
  const recenterId = `${event.block.number}_${event.log.logIndex}`;

  // Insert recenter record (ethBalance populated below after read)
  await context.db.insert(recenters).values({
    id: recenterId,
    timestamp: event.block.timestamp,
    currentTick: Number(currentTick),
    isUp,
    ethBalance: null,
    outstandingSupply: null,
    vwapTick: null,
  });

  // Update stats — increment counters (simple approach; API can do accurate rolling queries)
  const statsData = await context.db.find(stats, { id: STATS_ID });
  if (!statsData) return;

  await context.db.update(stats, { id: STATS_ID }).set({
    lastRecenterTimestamp: event.block.timestamp,
    lastRecenterTick: Number(currentTick),
    recentersLastDay: (statsData.recentersLastDay ?? 0) + 1,
    recentersLastWeek: (statsData.recentersLastWeek ?? 0) + 1,
  });
});

/**
 * Handle LiquidityManager EthScarcity events
 * Updates the most recent recenter record with ETH reserve and VWAP data
 * FIXED: Search for matching recenter by block + tick instead of assuming logIndex - 1
 */
ponder.on('LiquidityManager:EthScarcity', async ({ event, context }) => {
  const { currentTick, ethBalance, outstandingSupply, vwapTick } = event.args;

  // Strategy: Try logIndex-1 first (common case), then search by block+tick (fallback)
  // This handles both the common case efficiently and edge cases correctly
  let recenterId = `${event.block.number}_${event.log.logIndex - 1}`;
  let recenter = await context.db.find(recenters, { id: recenterId });

  // If logIndex-1 didn't work, search for matching recenter in same block by tick
  if (!recenter) {
    getLogger(context).warn(`EthScarcity: logIndex-1 failed for block ${event.block.number}. Searching by tick ${currentTick}...`);

    // Fallback: scan recent recenters from this block with matching tick
    // Build candidate IDs to check (scan backwards from current logIndex)
    for (let offset = 2; offset <= 10 && offset <= event.log.logIndex; offset++) {
      const candidateId = `${event.block.number}_${event.log.logIndex - offset}`;
      const candidate = await context.db.find(recenters, { id: candidateId });
      if (candidate && candidate.currentTick === Number(currentTick)) {
        recenter = candidate;
        recenterId = candidateId;
        getLogger(context).info(`EthScarcity: Found matching recenter at offset -${offset}`);
        break;
      }
    }
  }

  if (recenter) {
    await context.db.update(recenters, { id: recenterId }).set({
      ethBalance,
      outstandingSupply,
      vwapTick: Number(vwapTick),
    });
  } else {
    getLogger(context).error(
      `EthScarcity: No matching Recentered event found for block ${event.block.number}, tick ${currentTick}, logIndex ${event.log.logIndex}`
    );
  }

  // Update stats with reserve data, floor price, and 7d growth
  await updateReserveStats(context, event, ethBalance, currentTick, vwapTick);
});

/**
 * Handle LiquidityManager EthAbundance events
 * Updates the most recent recenter record with ETH reserve and VWAP data
 * FIXED: Search for matching recenter by block + tick instead of assuming logIndex - 1
 */
ponder.on('LiquidityManager:EthAbundance', async ({ event, context }) => {
  const { currentTick, ethBalance, outstandingSupply, vwapTick } = event.args;

  // Strategy: Try logIndex-1 first (common case), then search by block+tick (fallback)
  // This handles both the common case efficiently and edge cases correctly
  let recenterId = `${event.block.number}_${event.log.logIndex - 1}`;
  let recenter = await context.db.find(recenters, { id: recenterId });

  // If logIndex-1 didn't work, search for matching recenter in same block by tick
  if (!recenter) {
    getLogger(context).warn(`EthAbundance: logIndex-1 failed for block ${event.block.number}. Searching by tick ${currentTick}...`);

    // Fallback: scan recent recenters from this block with matching tick
    // Build candidate IDs to check (scan backwards from current logIndex)
    for (let offset = 2; offset <= 10 && offset <= event.log.logIndex; offset++) {
      const candidateId = `${event.block.number}_${event.log.logIndex - offset}`;
      const candidate = await context.db.find(recenters, { id: candidateId });
      if (candidate && candidate.currentTick === Number(currentTick)) {
        recenter = candidate;
        recenterId = candidateId;
        getLogger(context).info(`EthAbundance: Found matching recenter at offset -${offset}`);
        break;
      }
    }
  }

  if (recenter) {
    await context.db.update(recenters, { id: recenterId }).set({
      ethBalance,
      outstandingSupply,
      vwapTick: Number(vwapTick),
    });
  } else {
    getLogger(context).error(
      `EthAbundance: No matching Recentered event found for block ${event.block.number}, tick ${currentTick}, logIndex ${event.log.logIndex}`
    );
  }

  // Update stats with reserve data, floor price, and 7d growth
  await updateReserveStats(context, event, ethBalance, currentTick, vwapTick);
});

/**
 * Shared logic for EthScarcity and EthAbundance handlers:
 * Records ETH reserve in ring buffer, calculates 7d growth from ring buffer, floor price, and updates stats.
 */
async function updateReserveStats(
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  context: { db: any; log: any },
  event: { block: { number: bigint; timestamp: bigint }; log: { logIndex: number } },
  ethBalance: bigint,
  currentTick: number | bigint,
  vwapTick: number | bigint
) {
  // Record ETH reserve in ring buffer for hourly time-series
  await recordEthReserveSnapshot(context, ethBalance);

  // Compute 7d growth from ring buffer (slot 0 = ethReserve snapshots)
  const statsData = await context.db.find(stats, { id: STATS_ID });
  let ethReserve7dAgo: bigint | null = null;
  let ethReserveGrowthBps: number | null = null;

  if (statsData) {
    const ringBuffer = parseRingBuffer(statsData.ringBuffer as string[]);
    const pointer = statsData.ringBufferPointer ?? 0;

    // Walk backwards through ring buffer to find oldest non-zero ETH reserve
    for (let i = HOURS_IN_RING_BUFFER - 1; i >= 0; i--) {
      const baseIndex = ((pointer - i + HOURS_IN_RING_BUFFER) % HOURS_IN_RING_BUFFER) * RING_BUFFER_SEGMENTS;
      const reserve = ringBuffer[baseIndex + 0];
      if (reserve > 0n) {
        ethReserve7dAgo = reserve;
        break;
      }
    }

    if (ethReserve7dAgo && ethReserve7dAgo > 0n) {
      ethReserveGrowthBps = calculateBps(ethBalance, ethReserve7dAgo);
    }
  }

  // Calculate floor price (from vwapTick) and current price (from currentTick)
  const floorTick = Number(vwapTick);
  const floorPriceWei = priceFromTick(floorTick);
  const currentPriceWei = priceFromTick(Number(currentTick));

  // Calculate distance from floor in basis points
  const floorDistanceBps = calculateBps(currentPriceWei, floorPriceWei);

  // Update stats with all metrics
  await context.db.update(stats, { id: STATS_ID }).set({
    lastEthReserve: ethBalance,
    lastVwapTick: Number(vwapTick),
    ethReserve7dAgo,
    ethReserveGrowthBps,
    floorTick,
    floorPriceWei,
    currentPriceWei,
    floorDistanceBps,
  });
}
