import { ponder } from 'ponder:registry';
import { positions, transactions, stats, STATS_ID, TAX_RATES } from 'ponder:schema';
import {
  ensureStatsExists,
  getStakeTotalSupply,
  markPositionsUpdated,
  refreshOutstandingStake,
  updateHourlyData,
  checkBlockHistorySufficient,
  updateEthReserve,
} from './helpers/stats';
import type { StatsContext } from './helpers/stats';

// Pool address — staking/unstaking events keep lastEthReserve fresh alongside buy/sell events
const POOL_ADDRESS = (process.env.POOL_ADDRESS || '0x1f69cbfc7d3529a4fb4eadf18ec5644b2603b5ab') as `0x${string}`;

const ZERO = 0n;

async function getKraikenTotalSupply(context: StatsContext) {
  return context.client.readContract({
    abi: context.contracts.Kraiken.abi,
    address: context.contracts.Kraiken.address,
    functionName: 'totalSupply',
  });
}

function toShareRatio(share: bigint, stakeTotalSupply: bigint): number {
  if (stakeTotalSupply === 0n) return 0;
  return Number(share) / Number(stakeTotalSupply);
}

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

  const stakeTotalSupply = await getStakeTotalSupply(context);
  const shareRatio = toShareRatio(event.args.share, stakeTotalSupply);
  const totalSupplyInit = await getKraikenTotalSupply(context);

  const taxRateIndex = Number(event.args.taxRate);
  await context.db.insert(positions).values({
    id: event.args.positionId.toString(),
    owner: event.args.owner as `0x${string}`,
    share: shareRatio,
    taxRate: TAX_RATES[taxRateIndex] || 0,
    taxRateIndex,
    kraikenDeposit: event.args.kraikenDeposit,
    stakeDeposit: event.args.kraikenDeposit,
    taxPaid: ZERO,
    snatched: 0,
    creationTime: event.block.timestamp,
    lastTaxTime: event.block.timestamp,
    status: 'Active',
    createdAt: event.block.timestamp,
    totalSupplyInit,
    totalSupplyEnd: null,
    payout: ZERO,
  });

  // Record stake transaction
  await context.db.insert(transactions).values({
    id: `${event.transaction.hash}-${event.log.logIndex}`,
    holder: event.args.owner as `0x${string}`,
    type: 'stake',
    tokenAmount: event.args.kraikenDeposit,
    ethAmount: ZERO,
    timestamp: event.block.timestamp,
    blockNumber: Number(event.block.number),
    txHash: event.transaction.hash,
  });

  await refreshOutstandingStake(context);
  await markPositionsUpdated(context, event.block.timestamp);
  // Keep ETH reserve fresh — stake events may coincide with pool activity
  await updateEthReserve(context, POOL_ADDRESS);
});

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

  const positionId = event.args.positionId.toString();
  const position = await context.db.find(positions, { id: positionId });
  if (!position) return;

  const totalSupplyEnd = await getKraikenTotalSupply(context);

  await context.db.update(positions, { id: positionId }).set({
    status: 'Closed',
    closedAt: event.block.timestamp,
    totalSupplyEnd,
    payout: (position.payout ?? ZERO) + event.args.kraikenPayout,
    kraikenDeposit: ZERO,
    stakeDeposit: ZERO,
  });

  // Record unstake transaction (could be voluntary unstake or snatch payout)
  await context.db.insert(transactions).values({
    id: `${event.transaction.hash}-${event.log.logIndex}`,
    holder: position.owner,
    type: 'unstake',
    tokenAmount: event.args.kraikenPayout,
    ethAmount: ZERO,
    timestamp: event.block.timestamp,
    blockNumber: Number(event.block.number),
    txHash: event.transaction.hash,
  });

  await refreshOutstandingStake(context);
  await markPositionsUpdated(context, event.block.timestamp);
  // Keep ETH reserve fresh — unstake events may coincide with pool activity
  await updateEthReserve(context, POOL_ADDRESS);

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

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

  const positionId = event.args.positionId.toString();
  const position = await context.db.find(positions, { id: positionId });
  if (!position) return;

  const stakeTotalSupply = await getStakeTotalSupply(context);
  const shareRatio = toShareRatio(event.args.newShares, stakeTotalSupply);

  await context.db.update(positions, { id: positionId }).set({
    share: shareRatio,
    kraikenDeposit: BigInt(position.kraikenDeposit ?? ZERO) - event.args.kraikenPayout,
    stakeDeposit: BigInt(position.stakeDeposit ?? ZERO) - event.args.kraikenPayout,
    snatched: position.snatched + 1,
    payout: BigInt(position.payout ?? ZERO) + event.args.kraikenPayout,
  });

  await refreshOutstandingStake(context);
  await markPositionsUpdated(context, event.block.timestamp);

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

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

  const positionId = event.args.positionId.toString();
  const position = await context.db.find(positions, { id: positionId });
  if (!position) return;

  const stakeTotalSupply = await getStakeTotalSupply(context);
  const shareRatio = toShareRatio(event.args.newShares, stakeTotalSupply);

  const taxRateIndex = Number(event.args.taxRate);
  await context.db.update(positions, { id: positionId }).set({
    taxPaid: BigInt(position.taxPaid ?? ZERO) + event.args.taxPaid,
    share: shareRatio,
    taxRate: TAX_RATES[taxRateIndex] || position.taxRate,
    taxRateIndex,
    lastTaxTime: event.block.timestamp,
  });

  // Update totalTaxPaid counter (no longer ring-buffered)
  const statsData = await context.db.find(stats, { id: STATS_ID });
  if (statsData) {
    await context.db.update(stats, { id: STATS_ID }).set({
      totalTaxPaid: statsData.totalTaxPaid + event.args.taxPaid,
    });
  }

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

  await refreshOutstandingStake(context);
  await markPositionsUpdated(context, event.block.timestamp);
});

ponder.on('Stake:PositionRateHiked', async ({ event, context }) => {
  const positionId = event.args.positionId.toString();
  const taxRateIndex = Number(event.args.newTaxRate);
  await context.db.update(positions, { id: positionId }).set({
    taxRate: TAX_RATES[taxRateIndex] || 0,
    taxRateIndex,
  });
  await markPositionsUpdated(context, event.block.timestamp);
});
