import { callRpc } from '../../core/rpc';
import { getLatestBlockNumber, getBlockByNumber } from '../blocks/blocks';
import { getTransactionReceipt } from '../transactions/transactions';
import { 
  formatTokenAmount, 
  parseTimestampToMillis,
  parseHexToDecimal,
  formatDecimalToHex,
  formatWeiToEth
} from '../../utils/formatters';
import { 
  isRegisteredAddress, 
  getAddressName, 
} from '../../utils/registry';
import { AddressInfo, AddressTransaction, TransactionObject } from '../../types/address';
import { TokenBalance } from '../../types/token';
import { callExplorerApi } from '../../core/explorerApi';
import { logger } from '../../utils/logger';

/**
 * 
 * @param value 
 * @returns 
 */
function isTransactionObject(value: any): value is TransactionObject {
  return typeof value !== 'string' && value !== null && typeof value === 'object' && 'hash' in value;
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<string>} 
 */
export async function getAddressBalance(address: string): Promise<string> {
  return callRpc<string>('eth_getBalance', [address, 'latest']);
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<boolean>} 
 */
export async function isContract(address: string): Promise<boolean> {
  try {
    const code = await callRpc<string>('eth_getCode', [address, 'latest']);
    return Boolean(code && code !== '0x' && code !== '0x0');
  } catch (error) {
    console.error('Error checking if address is contract:', error);
    return false;
  }
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<number>} 
 */
export async function getAddressTransactionCount(address: string): Promise<number> {
  const result = await callRpc<string>('eth_getTransactionCount', [address, 'latest']);
  return parseHexToDecimal(result);
}

/**
 * 
 * @param {string} address 
 * @param {number} limit 
 * @param {number} page 
 * @returns {Promise<any>} 
 */
export async function getAddressTransactions(
  address: string, 
  limit: number = 20, 
  page: number = 1
): Promise<any> {
  try {
    const response = await callExplorerApi<any>(
      `address/${address}/transactions`, 
      { page, limit }
    );
    
    if (response.status === 'success' && Array.isArray(response.transactions)) {
      response.transactions = response.transactions.map((tx: any) => {
        let txFee = '0';
        
        const gasUsed = tx.gas_used || '0';
        const gasPrice = tx.gas_price || '0';
        
        if (gasUsed && gasPrice) {
          const gasUsedNum = typeof gasUsed === 'string' && gasUsed.startsWith('0x') 
            ? parseHexToDecimal(gasUsed) 
            : Number(gasUsed);
            
          const gasPriceNum = typeof gasPrice === 'string' && gasPrice.startsWith('0x') 
            ? parseHexToDecimal(gasPrice) 
            : Number(gasPrice);
            
          const feeValue = gasUsedNum * gasPriceNum;
          
          txFee = formatWeiToEth(feeValue, 8);
        }
        
        return {
          ...tx,
          txFee
        };
      });
    }
    
    return response;
  } catch (error) {
    console.error('Error fetching transactions from Explorer API:', error);


    try {
      const emptyResponse = {
        status: 'success',
        transactions: [],
        pagination: {
          currentPage: page,
          totalPages: 0,
          totalItems: 0,
          itemsPerPage: limit
        }
      };
      
      const { transactions, totalCount } = await fetchTransactionsViaRpc(address, limit, page);
      

      const enhancedTransactions = transactions.map(async (tx: AddressTransaction) => {
        try {
          const receipt = await getTransactionReceipt(tx.hash);
          
          if (receipt && receipt.gasUsed && receipt.effectiveGasPrice) {
            const gasUsedNum = parseHexToDecimal(receipt.gasUsed);
            const gasPriceNum = parseHexToDecimal(receipt.effectiveGasPrice);
            const feeValue = gasUsedNum * gasPriceNum;
            
            return {
              ...tx,
              txFee: formatWeiToEth(feeValue, 8)
            };
          }
          
          return tx;
        } catch (e) {
          console.warn(`Error calculating fee for transaction ${tx.hash}:`, e);
          return tx;
        }
      });
      

      const txsWithFees = await Promise.all(enhancedTransactions);
      
      if (txsWithFees.length > 0) {
        return {
          status: 'success',
          transactions: txsWithFees,
          pagination: {
            currentPage: page,
            totalPages: Math.ceil(totalCount / limit),
            totalItems: totalCount,
            itemsPerPage: limit
          }
        };
      }
      
      return emptyResponse;
    } catch (fallbackError) {
      console.error('Fallback mechanism also failed:', fallbackError);
      throw error; 
    }
  }
}


async function fetchTransactionsViaRpc(
  address: string, 
  limit: number = 20, 
  page: number = 1
): Promise<{
  transactions: AddressTransaction[],
  totalCount: number
}> {
  try {
    const latestBlockHex = await getLatestBlockNumber();
    const latestBlock = parseHexToDecimal(latestBlockHex);
    
    const transactions: AddressTransaction[] = [];
    const processedTxHashes = new Set<string>();
    
    const estimatedBlocksToScan = 500; 

    for (let i = 0; i < estimatedBlocksToScan; i++) {
      if (transactions.length >= limit * page) {
        break;
      }
      
      const blockNumber = latestBlock - i;
      if (blockNumber < 0) break;
      
      try {
        const block = await getBlockByNumber(blockNumber, true);
        
        if (!block || !block.transactions || !Array.isArray(block.transactions)) {
          continue;
        }
        
        const blockTimestamp = Math.floor(parseTimestampToMillis(block.timestamp) / 1000); 
        
        const relevantTxs = block.transactions.filter(tx => {
          if (!isTransactionObject(tx)) return false;
          
          return (
            (tx.from && tx.from.toLowerCase() === address.toLowerCase()) ||
            (tx.to && tx.to.toLowerCase() === address.toLowerCase())
          );
        }) as TransactionObject[];
        
        for (const tx of relevantTxs) {
          if (processedTxHashes.has(tx.hash)) continue;
          processedTxHashes.add(tx.hash);
          
          try {
            const receipt = await getTransactionReceipt(tx.hash);
            
            let status: 'success' | 'failed' | 'pending' = 'pending';
            if (receipt) {
              status = receipt.status === '0x1' ? 'success' : 'failed';
            }
            
            const value = tx.value || '0x0';
            
            const isContractCreation = !tx.to;
            
            transactions.push({
              hash: tx.hash,
              blockNumber: parseHexToDecimal(tx.blockNumber),
              timestamp: blockTimestamp,
              from: tx.from,
              to: tx.to,
              value: value,
              isContractCreation,
              status
            });
          } catch (e) {
            console.warn(`Error processing transaction ${tx.hash}:`, e);
          }
        }
      } catch (e) {
        console.warn(`Error processing block ${blockNumber}:`, e);
        continue;
      }
    }
    
    const totalCount = transactions.length;
    
    const startIndex = (page - 1) * limit;
    const paginatedTransactions = transactions.slice(startIndex, startIndex + limit);
    
    return {
      transactions: paginatedTransactions,
      totalCount
    };
  } catch (error) {
    console.error('Error getting address transactions via RPC:', error);
    return {
      transactions: [],
      totalCount: 0
    };
  }
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<TokenBalance[]>} 
 */
export async function getAddressTokenBalances(
  address: string
): Promise<TokenBalance[]> {
  try {
    if (!address || typeof address !== 'string') {
      console.warn('Invalid address provided to getAddressTokenBalances:', address);
      return [];
    }
    
    const normalizedAddress = address.startsWith('0x') ? address : `0x${address}`;
    if (normalizedAddress.length !== 42) {
      console.warn('Invalid address length:', normalizedAddress);
      return [];
    }
    
    logger.debug('Getting token balances for address:', normalizedAddress);
    
    try {
      await getAddressBalance(normalizedAddress);
    } catch (error) {
      console.error('Error validating address with eth_getBalance:', error);
      return [];
    }
      const latestBlockHex = await getLatestBlockNumber();

    const transferEventSignature = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
    
    const addressWithoutPrefix = normalizedAddress.slice(2).toLowerCase();
    const toTopicAddress = '0x000000000000000000000000' + addressWithoutPrefix;
    
    const blockRange = 10000;
    const fromBlock = Math.max(0, parseHexToDecimal(latestBlockHex) - blockRange);
    const fromBlockHex = formatDecimalToHex(fromBlock);
    
    logger.debug(`Querying token transfers from block ${fromBlockHex} to latest for address ${normalizedAddress}`);
    
    const logs = await callRpc<any[]>('eth_getLogs', [{
      fromBlock: fromBlockHex,
      toBlock: 'latest',
      topics: [
        transferEventSignature,
        null,
        toTopicAddress 
      ]
    }]);
    
    const tokenAddresses = [...new Set(logs.map(log => log.address))];
    
    logger.debug(`Found ${tokenAddresses.length} potential token contracts for address ${normalizedAddress}`);
    
    if (tokenAddresses.length === 0) {
      return [];
    }
    
    const tokenPromises = tokenAddresses.map(async (tokenAddress) => {
      try {
        const balanceOfSignature = '0x70a08231'; 
        const nameSignature = '0x06fdde03';
        const symbolSignature = '0x95d89b41'; 
        const decimalsSignature = '0x313ce567'; 
        
        const paddedAddress = '000000000000000000000000' + addressWithoutPrefix;
        
        const [balanceResult, nameResult, symbolResult, decimalsResult] = await Promise.all([
          callRpc<string>('eth_call', [{
            to: tokenAddress,
            data: balanceOfSignature + paddedAddress
          }, 'latest']),
          
          callRpc<string>('eth_call', [{
            to: tokenAddress,
            data: nameSignature
          }, 'latest']).catch(() => '0x'),
          
          callRpc<string>('eth_call', [{
            to: tokenAddress,
            data: symbolSignature
          }, 'latest']).catch(() => '0x'),
          
          callRpc<string>('eth_call', [{
            to: tokenAddress,
            data: decimalsSignature
          }, 'latest']).catch(() => '0x0000000000000000000000000000000000000000000000000000000000000012') // Default to 18 decimals
        ]);
        
        let balance = balanceResult ? parseHexToDecimal(balanceResult) : 0;
        
        if (balance === 0) {
          return null;
        }
        
        let decimals = 18;
        if (decimalsResult && decimalsResult !== '0x') {
          decimals = parseHexToDecimal(decimalsResult);
        }
        
        let name = 'Unknown Token';
        let symbol = 'UNKNOWN';
        
        if (nameResult && nameResult !== '0x') {
          try {
            const nameHex = nameResult.slice(2); 
            const nameLength = parseHexToDecimal('0x' + nameHex.slice(64, 128));
            const nameData = nameHex.slice(128, 128 + nameLength * 2); 
            name = Buffer.from(nameData, 'hex').toString('utf8');
          } catch (e) {
            console.error('Error parsing token name:', e);
          }
        }
        
        if (symbolResult && symbolResult !== '0x') {
          try {
            const symbolHex = symbolResult.slice(2); 
            const symbolLength = parseHexToDecimal('0x' + symbolHex.slice(64, 128));
            const symbolData = symbolHex.slice(128, 128 + symbolLength * 2); 
            symbol = Buffer.from(symbolData, 'hex').toString('utf8');
          } catch (e) {
            console.error('Error parsing token symbol:', e);
          }
        }
        
        const balanceInTokens = balance / Math.pow(10, decimals);
        
        if (balanceInTokens < 0.000001) {
          return null;
        }
        
        const formattedBalance = formatTokenAmount(balance.toString(), decimals);
        
        return {
          token: symbol,
          name: name,
          balance: formattedBalance,
          rawBalance: balanceInTokens.toString(),
          address: tokenAddress,
          value: `${formattedBalance} ${symbol}`,
          decimals: decimals
        };
      } catch (error) {
        console.error(`Error fetching data for token at ${tokenAddress}:`, error);
        return null;
      }
    });
    

    const tokenResults = await Promise.all(tokenPromises);
    
    const tokenBalances = tokenResults
      .filter((result): result is TokenBalance => result !== null)
      .sort((a, b) => parseFloat(b.rawBalance) - parseFloat(a.rawBalance));
    
    logger.debug(`Found ${tokenBalances.length} tokens with non-zero balance for address ${normalizedAddress}`);
    
    return tokenBalances;
  } catch (error) {
    console.error('Error fetching token balances:', error);
    return [];
  }
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<{creator: string | null, creationTx: string | null, creationTimestamp: number | null}>}
 */
export async function getContractCreationInfo(address: string): Promise<{
  creator: string | null,
  creationTx: string | null,
  creationTimestamp: number | null
}> {
  try {
    const response = await callExplorerApi<any>(`contracts/${address}`);
    
    if (response && response.data) {
      const contractData = response.data;
      return {
        creator: contractData.creator_address || null,
        creationTx: contractData.creation_transaction || null,
        creationTimestamp: contractData.creation_timestamp ? 
          parseInt(contractData.creation_timestamp) : null
      };
    }
    
    return {
      creator: null,
      creationTx: null,
      creationTimestamp: null
    };
  } catch (error) {
    console.warn(`Could not fetch contract info from Explorer DB: ${error}`);
    return {
      creator: null,
      creationTx: null,
      creationTimestamp: null
    };
  }
}

/**
 * 
 * @param {string} address 
 * @returns {Promise<AddressInfo>} 
 */
export async function getAddressInfo(address: string): Promise<AddressInfo> {
  try {
    const [balance, isContractAddr, txCount, tokenBalances] = await Promise.all([
      getAddressBalance(address),
      isContract(address),
      getAddressTransactionCount(address),
      getAddressTokenBalances(address)
    ]);
    
    const isSystemAddr = isRegisteredAddress(address);
    const description = getAddressName(address);
    
    let creator = null;
    let creationTx = null;
    let creationTimestamp = null;
    
    if (isContractAddr) {
      const contractInfo = await getContractCreationInfo(address);
      creator = contractInfo.creator;
      creationTx = contractInfo.creationTx;
      creationTimestamp = contractInfo.creationTimestamp;
    }
    
    return {
      address,
      balance,
      transactionCount: txCount,
      isContract: isContractAddr,
      isSystemAddress: isSystemAddr,
      description: description || undefined,
      tokenBalances,
      creator,
      creationTx,
      creationTimestamp
    };
  } catch (error) {
    console.error('Error getting address info:', error);
    throw error;
  }
}

/**
 * 
 * @param {string} address 
 * @param {string} methodName 
 * @param {object} options 
 * @returns {Promise<any>} 
 */
export async function getAddressTransactionsByMethod(
  address: string,
  methodName: string,
  options: Record<string, any> = {}
): Promise<any> {
  try {
    const normalizedAddress = address.toLowerCase();
    
    const params: Record<string, any> = {
      ...options,
      method: methodName
    };

    const response = await callExplorerApi<any>(`address/${normalizedAddress}/transactions`, params);
    
    if (response && response.transactions && Array.isArray(response.transactions)) {
      if (methodName.toLowerCase() === 'approve') {
        response.transactions = await Promise.all(response.transactions.map(async (tx: any) => {
          if (tx.to_address) {
            try {
              return {
                ...tx,
                token_symbol: 'tEXT' 
              };
            } catch (error) {
              console.warn(`Could not get token info for ${tx.to_address}:`, error);
            }
          }
          return tx;
        }));
      }
    }
    
    return response;
  } catch (error: any) {
    console.error(`Error fetching ${methodName} transactions for address ${address}:`, error);
    throw new Error(`Failed to get ${methodName} transactions: ${error.message}`);
  }
} 