

import { TokenHolder } from '../../types';
import { callRpc } from '../../core/rpc';
import { getBlockByNumber, getLatestBlockNumber } from '../blocks/blocks';
import { 
  formatTokenAmount, 
  parseBlockchainTimestamp, 
  parseHexToDecimal, 
  formatDecimalToHex 
} from '../../utils/formatters';
import { getTokenInfo } from './tokens';

/**
 
 * @param {string} tokenAddress 
 * @param {number} limit 
 * @returns {Promise<TokenHolder[]>}
 */
export async function getTokenHolders(
  tokenAddress: string,
  limit: number = 20
): Promise<TokenHolder[]> {
  try {
    const normalizedAddress = tokenAddress.startsWith('0x') ? tokenAddress : `0x${tokenAddress}`;
    
    if (normalizedAddress.length !== 42) {
      throw new Error(`Invalid token address format: ${normalizedAddress}`);
    }
    
    const code = await callRpc<string>('eth_getCode', [normalizedAddress, 'latest']);
    const isContract = code && code !== '0x' && code !== '0x0';
    
    if (!isContract) {
      throw new Error(`Address ${normalizedAddress} is not a contract`);
    }
    
    const { decimals } = await getTokenInfo(normalizedAddress);
    
    const transferEventSignature = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
    const balanceOfSignature = '0x70a08231';
    
    const latestBlockHex = await getLatestBlockNumber();
    
    const blockRange = 10000;
    const fromBlock = Math.max(0, parseHexToDecimal(latestBlockHex) - blockRange);
    const fromBlockHex = formatDecimalToHex(fromBlock);
    
    const logs = await callRpc<any[]>('eth_getLogs', [{
      address: normalizedAddress,
      fromBlock: fromBlockHex,
      toBlock: 'latest',
      topics: [transferEventSignature]
    }]);
    
    const balanceMap = new Map<string, bigint>();
    const lastUpdateMap = new Map<string, number>();
    
    const totalSupplyBigInt = BigInt(await callRpc<string>('eth_call', [{
      to: normalizedAddress,
      data: '0x18160ddd'
    }, 'latest']).catch(() => '0x0'));
    
    for (const log of logs) {
      if (log.topics && log.topics.length >= 3) {
        const fromAddress = '0x' + log.topics[1].slice(26).toLowerCase();
        const toAddress = '0x' + log.topics[2].slice(26).toLowerCase();
        
        const value = BigInt(log.data);
        
       
        const blockNumber = parseHexToDecimal(log.blockNumber);
        const block = await getBlockByNumber(blockNumber, false);
        let timestamp = Math.floor(Date.now() / 1000); 
        
        if (block && block.timestamp) {
          try {
            const date = parseBlockchainTimestamp(block.timestamp);
            timestamp = Math.floor(date.getTime() / 1000);
          } catch (error) {
            console.error('Error parsing block timestamp:', error);
          }
        }
        
        const currentFromBalance = balanceMap.get(fromAddress) || BigInt(0);
        balanceMap.set(fromAddress, currentFromBalance - value);
        lastUpdateMap.set(fromAddress, timestamp);
        
        const currentToBalance = balanceMap.get(toAddress) || BigInt(0);
        balanceMap.set(toAddress, currentToBalance + value);
        lastUpdateMap.set(toAddress, timestamp);
      }
    }
    
    const holders: TokenHolder[] = [];
    
    for (const [address, estimatedBalance] of balanceMap.entries()) {
      if (estimatedBalance <= BigInt(0) && address !== '0x0000000000000000000000000000000000000000') continue;
      
      try {
        const paddedAddress = '000000000000000000000000' + address.slice(2);
        
        const balanceHex = await callRpc<string>('eth_call', [{
          to: normalizedAddress,
          data: balanceOfSignature + paddedAddress
        }, 'latest']);
        
        const balanceBigInt = BigInt(balanceHex);
        
        if (balanceBigInt <= BigInt(0)) continue;
        
        const percentage = totalSupplyBigInt > BigInt(0) 
          ? Number((balanceBigInt * BigInt(10000)) / totalSupplyBigInt) / 100
          : 0;
        
        const balanceInTokens = Number(balanceBigInt) / Math.pow(10, decimals);
        
        const formattedBalance = formatTokenAmount(balanceBigInt.toString(), decimals);
        
        const lastUpdated = lastUpdateMap.get(address) || Math.floor(Date.now() / 1000);
        
        const isZeroAddress = address === '0x0000000000000000000000000000000000000000';
        
        holders.push({
          address,
          balance: formattedBalance,
          rawBalance: balanceBigInt.toString(),
          percentage: percentage.toFixed(2) + '%',
          lastUpdated,
          label: isZeroAddress ? 'Burned Tokens' : undefined
        });
      } catch (e) {
        console.warn(`Error getting balance for ${address}:`, e);
      }
    }
    
    return holders
      .sort((a, b) => BigInt(b.rawBalance) > BigInt(a.rawBalance) ? 1 : -1)
      .slice(0, limit);
  } catch (error) {
    console.error('Error getting token holders:', error);
    return [];
  }
} 