

import { Block, PaginatedBlocks, BlockchainStats, EnhancedBlock } from '../../types';
import { callRpc } from '../../core/rpc';
import { callExplorerApi } from '../../core/explorerApi';
import { formatBlockForDisplay, FormattedBlock } from './utils';
import { 
  parseBlockchainTimestamp, 
  parseHexToDecimal, 
  standardizeTimestamp 
} from '../../utils/formatters';
import { logger } from '../../utils/logger';

/**
 * 
 * @returns {Promise<string>} 
 */
export async function getLatestBlockNumber(): Promise<string> {
  return callRpc<string>('eth_blockNumber');
}

/**
 * 
 * @param {string|number} blockNumber 
 * @param {boolean} includeTransactions 
 * @returns {Promise<Block>} 
 */
export async function getBlockByNumber(
  blockNumber: string | number, 
  includeTransactions = false
): Promise<Block> {
  const blockNumberHex = typeof blockNumber === 'number' 
    ? '0x' + blockNumber.toString(16) 
    : blockNumber;
  
  const block = await callRpc<Block>('eth_getBlockByNumber', [blockNumberHex, includeTransactions]);
  
  return block;
}

/**
 * 
 * @param {string} blockHash 
 * @param {boolean} includeTransactions 
 * @returns {Promise<Block>} 
 */
export async function getBlockByHash(
  blockHash: string, 
  includeTransactions = false
): Promise<Block> {
  const block = await callRpc<Block>('eth_getBlockByHash', [blockHash, includeTransactions]);
  
  return block;
}

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


/**
 * 
 * @param {number} page 
 * @param {number} perPage 
 * @param {boolean} includeTransactions 
 * @param {boolean} debug 
 * @returns {Promise<PaginatedBlocks>} 
 */
export async function getLatestBlocks(
  page = 1,
  perPage = 10,
  includeTransactions = false,
  debug = false
): Promise<PaginatedBlocks> {
  try {
    interface ExplorerBlocksResponse {
      data?: any[];
      blocks?: any[];
      meta?: {
        total?: number;
        count?: number;
        total_pages?: number;
      };
      totalCount?: number;
      total?: number;
      count?: number;
      status?: string;
    }

    if (debug) logger.debug(`Fetching blocks: page=${page}, perPage=${perPage}, includeTransactions=${includeTransactions}`);

    const response = await callExplorerApi<ExplorerBlocksResponse>('blocks', {
      limit: perPage,
      offset: (page - 1) * perPage,
      include_transactions: includeTransactions ? 1 : 0
    });

    if (debug) {
      logger.debug('Full API Response Structure:', JSON.stringify(response, null, 2).substring(0, 300) + '...');
      logger.debug('Response received, processing blocks...');
    }

    let blockData: any[] = [];
    
    if (Array.isArray(response)) {
      blockData = response;
    } else if (response.data && Array.isArray(response.data)) {
      blockData = response.data;
    } else if (response.blocks && Array.isArray(response.blocks)) {
      blockData = response.blocks;
    } else {
      logger.error('Unexpected API response format:', response);
      throw new Error('Unexpected API response format. Check logs for details.');
    }

    if (debug) logger.debug(`Processing ${blockData.length} blocks...`);
    
    const blocks: FormattedBlock[] = blockData.map((blockData: any, index: number) => {
      const block: Block = {
        number: blockData.number 
          ? (blockData.number.startsWith?.('0x') ? blockData.number : `0x${parseInt(blockData.number).toString(16)}`)
          : '0x0',
        hash: blockData.hash || '',
        timestamp: standardizeTimestamp(blockData.timestamp, true) || Date.now(),
        parentHash: blockData.parent_hash || blockData.parentHash || '',
        miner: blockData.miner || blockData.validator || '',
        transactions: blockData.transactions || [],
        txCount: blockData.transactions_count || blockData.transactionsCount || (blockData.transactions ? blockData.transactions.length : 0),
        size: blockData.size 
          ? (blockData.size.startsWith?.('0x') ? blockData.size : `0x${parseInt(blockData.size).toString(16)}`) 
          : undefined,
        gasUsed: blockData.gas_used || blockData.gasUsed 
          ? (String(blockData.gas_used || blockData.gasUsed).startsWith('0x') 
              ? String(blockData.gas_used || blockData.gasUsed) 
              : `0x${parseInt(String(blockData.gas_used || blockData.gasUsed)).toString(16)}`) 
          : undefined,
        gasLimit: blockData.gas_limit || blockData.gasLimit
          ? (String(blockData.gas_limit || blockData.gasLimit).startsWith('0x') 
              ? String(blockData.gas_limit || blockData.gasLimit) 
              : `0x${parseInt(String(blockData.gas_limit || blockData.gasLimit)).toString(16)}`) 
          : undefined,
        baseFeePerGas: blockData.base_fee_per_gas || blockData.baseFeePerGas
          ? (String(blockData.base_fee_per_gas || blockData.baseFeePerGas).startsWith('0x') 
              ? String(blockData.base_fee_per_gas || blockData.baseFeePerGas) 
              : `0x${parseInt(String(blockData.base_fee_per_gas || blockData.baseFeePerGas)).toString(16)}`) 
          : undefined,
        difficulty: blockData.difficulty || '',
        totalDifficulty: blockData.total_difficulty || blockData.totalDifficulty || '',
        extraData: blockData.extra_data || blockData.extraData || '',
        stateRoot: blockData.state_root || blockData.stateRoot || '',
        receiptsRoot: blockData.receipts_root || blockData.receiptsRoot || '',
        logsBloom: blockData.logs_bloom || blockData.logsBloom || '',
        nonce: blockData.nonce || '',
        mixHash: blockData.mix_hash || blockData.mixHash || '',
        sha3Uncles: blockData.sha3_uncles || blockData.sha3Uncles || '',
        status: blockData.status || blockData.batch_status || '',
        batchId: blockData.batch_id || blockData.batchId,
        batchStatus: blockData.batch_status || blockData.batchStatus
      };
      
      return formatBlockForDisplay(block);
    });
    
    const totalCount = 
      (response.meta && response.meta.total) || 
      response.total || 
      response.totalCount || 
      response.count || 
      (response.meta && response.meta.count) || 
      blocks.length;
    
    const totalPages = 
      (response.meta && response.meta.total_pages) || 
      Math.ceil(totalCount / perPage);
    
    if (debug) logger.debug(`Returning ${blocks.length} blocks, totalCount: ${totalCount}, totalPages: ${totalPages}`);
    
    return {
      blocks,
      totalCount,
      totalPages,
      page,
      perPage
    };
  } catch (error: any) {
    logger.error('Error fetching blocks from Explorer DB:', error);
    throw new Error(`Failed to get blocks from Explorer DB: ${error.message}`);
  }
}

/**
 * 
 * @param {number} sampleSize 
 * @returns {Promise<BlockchainStats>} 
 */
export async function getBlockchainStats(
  sampleSize = 10
): Promise<BlockchainStats> {
  try {
    const latestBlockHex = await getLatestBlockNumber();
    const latestBlockNumber = parseHexToDecimal(latestBlockHex);
    
    const recentBlocks: Block[] = [];
    
    for (let i = 0; i < sampleSize; i++) {
      const blockNumber = latestBlockNumber - i;
      if (blockNumber < 0) break;
      
      const block = await getBlockByNumber(blockNumber, false);
      if (block) recentBlocks.push(block);
    }
    
    let avgBlockTime = 0;
    let blockTimeChange = 0;
    
    if (recentBlocks.length >= 2) {
      const blockTimes = [];
      for (let i = 1; i < recentBlocks.length; i++) {
        try {
          const prevDate = parseBlockchainTimestamp(recentBlocks[i-1].timestamp);
          const currDate = parseBlockchainTimestamp(recentBlocks[i].timestamp);
          
          const prevTimestamp = prevDate.getTime() / 1000;
          const currTimestamp = currDate.getTime() / 1000;
          
          blockTimes.push(prevTimestamp - currTimestamp);
        } catch (error) {
          logger.error('Error parsing block timestamp:', error);
          continue;
        }
      }
      
      avgBlockTime = blockTimes.reduce((sum, time) => sum + time, 0) / blockTimes.length;
      
      if (blockTimes.length >= 6) {
        const halfIndex = Math.floor(blockTimes.length / 2);
        const firstHalf = blockTimes.slice(0, halfIndex).reduce((sum, time) => sum + time, 0) / halfIndex;
        const secondHalf = blockTimes.slice(halfIndex).reduce((sum, time) => sum + time, 0) / (blockTimes.length - halfIndex);
        blockTimeChange = ((firstHalf - secondHalf) / secondHalf) * 100;
      }
    }
    
    const txCounts = await Promise.all(
      recentBlocks.slice(0, 5).map(block => {
        if (Array.isArray(block.transactions)) {
          return block.transactions.length;
        } else {
          return getBlockTransactionCount(block.number);
        }
      })
    );
    
    const avgTxPerBlock = txCounts.reduce((sum, count) => sum + count, 0) / txCounts.length;
    
    const totalTransactions = Math.round(latestBlockNumber * avgTxPerBlock);
    
    let txPerBlockChange = 0;
    if (txCounts.length >= 6) {
      const halfIndex = Math.floor(txCounts.length / 2);
      const firstHalf = txCounts.slice(0, halfIndex).reduce((sum, count) => sum + count, 0) / halfIndex;
      const secondHalf = txCounts.slice(halfIndex).reduce((sum, count) => sum + count, 0) / (txCounts.length - halfIndex);
      txPerBlockChange = ((firstHalf - secondHalf) / secondHalf) * 100;
    }
    
    return {
      totalBlocks: latestBlockNumber + 1,
      avgBlockTime,
      blockTimeChange,
      avgTxPerBlock,
      txPerBlockChange,
      totalTransactions
    };
  } catch (error) {
    logger.error('Error calculating blockchain stats:', error);
    throw error;
  }
}



async function enhanceTransactionsWithReceipts(transactions: any[]): Promise<any[]> {
  try {
    if (!transactions || transactions.length === 0 || typeof transactions[0] === 'string') {
      return transactions;
    }

    const { getTransactionReceipt } = require('../transactions');
    

    const enhancedTransactions = await Promise.all(
      transactions.map(async (tx: any) => {
        try {
          const receipt = await getTransactionReceipt(tx.hash);
          
          return {
            ...tx,
            gasUsed: receipt.gasUsed,
            effectiveGasPrice: receipt.effectiveGasPrice,
            status: receipt.status,
            cumulativeGasUsed: receipt.cumulativeGasUsed,
            contractAddress: receipt.contractAddress
          };
        } catch (error) {
          logger.error(`Error getting receipt for transaction ${tx.hash}:`, error);
          return tx; 
        }
      })
    );
    
    return enhancedTransactions;
  } catch (error) {
    logger.error('Error enhancing transactions with receipts:', error);
    return transactions; 
  }
}

/**
 * 
 * @param {string} blockId 
 * @param {boolean} includeTransactions 
 * @returns {Promise<EnhancedBlock>} 
 */
export async function getEnhancedBlockDetails(
  blockId: string,
  includeTransactions = true
): Promise<EnhancedBlock> {
  let blockData: Block;
  let blockNumber: number;
  
  if (blockId.startsWith('B-')) {
    blockNumber = parseInt(blockId.replace('B-', ''));
    
    if (isNaN(blockNumber)) {
      throw new Error('Invalid block number format');
    }
    
    blockData = await getBlockByNumber(blockNumber, includeTransactions);
  } else if (blockId.startsWith('0x')) {
    blockData = await getBlockByHash(blockId, includeTransactions);
    blockNumber = parseHexToDecimal(blockData.number);
  } else {
    blockNumber = parseInt(blockId);
    
    if (isNaN(blockNumber)) {
      throw new Error('Invalid block identifier');
    }
    
    blockData = await getBlockByNumber(blockNumber, includeTransactions);
  }
  
  if (!blockData) {
    throw new Error('Block data not found');
  }
  
  let batchId = undefined;
  let batchStatus = undefined;
  
  try {
    logger.debug(`Fetching batch information for block ${blockNumber} from Explorer DB`);
    const explorerBlockData = await callExplorerApi<any>(`blocks/${blockNumber}`);
    
    if (explorerBlockData && explorerBlockData.data) {
      batchId = explorerBlockData.data.batch_id;
      batchStatus = explorerBlockData.data.batch_status;
      
      logger.debug(`Found batch info for block ${blockNumber}: ID=${batchId}, Status=${batchStatus}`);
      
      blockData.batchId = batchId;
      blockData.batchStatus = batchStatus;
    }
  } catch (error) {
    logger.warn(`Could not fetch batch information from Explorer DB for block ${blockNumber}:`, error);
  }

  if (Array.isArray(blockData.transactions) && blockData.transactions.length > 0 && typeof blockData.transactions[0] === 'object') {
    try {

      const blockDate = parseBlockchainTimestamp(blockData.timestamp);
      const blockTimestamp = blockDate.getTime();
      

      blockData.transactions = blockData.transactions.map((tx: any) => {
        if (!tx.timestamp) {
          return { ...tx, timestamp: blockTimestamp };
        }
        return tx;
      });
      

      if (includeTransactions) {
        blockData.transactions = await enhanceTransactionsWithReceipts(blockData.transactions);
      }
    } catch (error) {
      logger.error('Error processing block timestamp for transactions:', error);

    }
  }
  

  const formattedBlock = formatBlockForDisplay(blockData);
  

  let formattedTransactions: any[] = [];
  if (Array.isArray(blockData.transactions)) {
    if (blockData.transactions.length > 0 && typeof blockData.transactions[0] === 'object') {
      try {
        const { formatTransactionsForDisplay } = require('../transactions');
        if (typeof formatTransactionsForDisplay === 'function') {
          formattedTransactions = formatTransactionsForDisplay(blockData.transactions);
        } else {
          formattedTransactions = blockData.transactions.map((tx: any) => ({
            hash: tx.hash,
            from: tx.from,
            to: tx.to,
            value: tx.value || '0',
            status: tx.status || 'unknown',
            gasUsed: tx.gasUsed,
            gasPrice: tx.gasPrice,
            gas: tx.gas,
            nonce: tx.nonce,
            input: tx.input,
            timestamp: formattedBlock.timestamp
          }));
        }
      } catch (error) {
        logger.error('Error formatting transactions:', error);
        formattedTransactions = blockData.transactions.map((tx: any) => ({
          hash: tx.hash,
          from: tx.from || 'Unknown',
          to: tx.to || 'Unknown',
          value: tx.value || '0',
          status: tx.status || 'unknown',
          gasUsed: tx.gasUsed,
          gasPrice: tx.gasPrice,
          gas: tx.gas,
          nonce: tx.nonce,
          input: tx.input,
          timestamp: formattedBlock.timestamp
        }));
      }
    } else {
      formattedTransactions = (blockData.transactions as string[]).map((hash: string) => ({
        hash,
        from: 'Unknown',
        to: 'Unknown',
        value: '0',
        status: 'unknown',
        timestamp: formattedBlock.timestamp
      }));
    }
  }
  
  const rootHash = blockData.stateRoot || blockData.receiptsRoot || undefined;
  
  let l1BlockNumber: number | undefined = undefined;
  if (blockData.l1BlockNumber) {
    if (typeof blockData.l1BlockNumber === 'string' && blockData.l1BlockNumber.startsWith('0x')) {
      l1BlockNumber = parseInt(blockData.l1BlockNumber, 16);
    } else {
      l1BlockNumber = Number(blockData.l1BlockNumber);
    }
  }
  
  const enhancedBlock: EnhancedBlock = {
    ...formattedBlock,
    transactions: formattedTransactions,
    txCount: formattedTransactions.length,
    transferTxCount: formattedTransactions.filter((tx: any) => 
      tx.method === 'Transfer'
    ).length,
    contractTxCount: formattedTransactions.filter((tx: any) => 
      tx.method === 'Contract Call' || tx.method === 'Contract Creation'
    ).length,
    miner: blockData.miner,
    validator: blockData.validator || formattedBlock.validator,
    parentHash: blockData.parentHash,
    rootHash: rootHash,
    stateRoot: blockData.stateRoot,
    receiptsRoot: blockData.receiptsRoot,
    extraData: blockData.extraData,
    validatorBlocks: Math.floor(Math.random() * 100) + 50,
    difficulty: blockData.difficulty,
    totalDifficulty: blockData.totalDifficulty,
    nonce: blockData.nonce,
    mixHash: blockData.mixHash,
    sha3Uncles: blockData.sha3Uncles,
    logsBloom: blockData.logsBloom,
    sendCount: blockData.sendCount,
    sendRoot: blockData.sendRoot,
    l1BlockNumber,
    batchId: blockData.batchId,
    batchStatus: blockData.batchStatus
  };
  
  return enhancedBlock;
} 