/**
 * Transaction Operations
 */

import { Transaction, RpcTxStatus } from '../../types';
import { callRpc } from '../../core/rpc';
import { callExplorerApi } from '../../core/explorerApi';
import { getBlockByNumber, getLatestBlockNumber } from '../blocks/blocks';
import { 
  formatWeiToEth, 
  parseTimestampToMillis,
  parseHexToDecimal,
  standardizeTimestamp,
  formatRelativeTime
} from '../../utils/formatters';
import { logger } from '../../utils/logger';


export interface TransactionReceipt {
  transactionHash: string;
  transactionIndex: string;
  blockHash: string;
  blockNumber: string;
  from: string;
  to: string | null;
  cumulativeGasUsed: string;
  gasUsed: string;
  contractAddress: string | null;
  logs: unknown[];
  logsBloom: string;
  status: string;
  effectiveGasPrice: string;
  type: string;
}

/**
 * @param {string} txHash 
 * @returns {Promise<Transaction>} 
 */
export async function getTransactionByHash(txHash: string): Promise<Transaction> {
  return callRpc<Transaction>('eth_getTransactionByHash', [txHash]);
}

/**
 * @param {string} txHash 
 * @returns {Promise<TransactionReceipt>} 
 */
export async function getTransactionReceipt(txHash: string): Promise<TransactionReceipt> {
  return callRpc<TransactionReceipt>('eth_getTransactionReceipt', [txHash]);
}

/**

 * @param {number} count 
 * @returns {Promise<Transaction[]>} 
 */
export async function getLatestTransactions(count = 10): Promise<Transaction[]> {
  try {
    const latestBlockHex = await getLatestBlockNumber();
    const latestBlock = parseHexToDecimal(latestBlockHex);
    

    const transactions: Transaction[] = [];
    const blocksToFetch = Math.min(count, 10);
    
    for (let i = 0; i < blocksToFetch; i++) {
      const blockNumber = latestBlock - i;
      const blockData = await getBlockByNumber(blockNumber, true);
      
      if (blockData && blockData.transactions) {
        const blockTimestamp = blockData.timestamp;
        const txsWithTimestamp = (blockData.transactions as Transaction[]).map(tx => ({
          ...tx,
          timestamp: blockTimestamp,
          blockHash: blockData.hash,
        }));
        
        transactions.push(...txsWithTimestamp);
        
        if (transactions.length >= count) {
          break;
        }
      }
    }
    
    return transactions.slice(0, count);
  } catch (error) {
    logger.error('Error getting latest transactions:', error);
    throw error;
  }
}

/**

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

/**

 * @param {Transaction[]} transactions 
 * @returns {Transaction[]} 
 */
export function formatTransactionsForDisplay(transactions: Transaction[]): Transaction[] {
  return transactions.map(tx => {
    let txFee = '0';

    const gasUsed = tx.gasUsed || '0x0';
    const gasPrice = tx.gasPrice || tx.effectiveGasPrice || '0x0';
    const gas = tx.gas || '0x0';
    
    if (tx.gasUsed && tx.effectiveGasPrice) {
      const gasUsedNum = parseHexToDecimal(tx.gasUsed);
      const gasPriceNum = parseHexToDecimal(tx.effectiveGasPrice);
      const feeValue = gasUsedNum * gasPriceNum;
      txFee = formatWeiToEth(feeValue, 8).replace(' tEXT', '');
    } else if (tx.gas && tx.gasPrice) {
      const gasNum = parseHexToDecimal(tx.gas);
      const gasPriceNum = parseHexToDecimal(tx.gasPrice);
      const feeValue = gasNum * gasPriceNum;
      txFee = formatWeiToEth(feeValue, 8).replace(' tEXT', '');
    }
    
    let status: 'success' | 'pending' | 'failed' = 'pending';
    if (tx.blockNumber) {
      const txStatus = tx.status as RpcTxStatus;
      status = txStatus === '0x1' ? 'success' : 
               txStatus === '0x0' ? 'failed' : 'success';
    }
    
    const capitalizedStatus = status.charAt(0).toUpperCase() + status.slice(1);
    
    let valueFormatted;
    try {
      if (tx.value) {
        valueFormatted = formatWeiToEth(tx.value, 6);
      } else {
        valueFormatted = '0 tEXT';
      }
    } catch (e) {
      logger.error('Error parsing transaction value:', e);
      valueFormatted = '0 tEXT';
    }
    
    const timestamp = parseTimestampToMillis(tx.timestamp || Date.now());
    
    const age = formatRelativeTime(timestamp);
    
    let blockNumberFormatted;
    let blockNumberValue;
    try {
      if (tx.blockNumber) {
        blockNumberFormatted = tx.blockNumber.toString();
        blockNumberValue = typeof tx.blockNumber === 'string' ? 
          (tx.blockNumber.startsWith('0x') ? parseInt(tx.blockNumber, 16) : parseInt(tx.blockNumber)) : 
          tx.blockNumber;
      } else {
        blockNumberFormatted = 'Pending';
        blockNumberValue = null;
      }
    } catch (e) {
      logger.error('Error parsing block number:', e);
      blockNumberFormatted = 'Unknown';
      blockNumberValue = null;
    }
    
    let methodName = 'Transfer';
    let methodSignature = '';
    let methodDisplayName = '';
    
    if (tx.method_name) {
      methodName = tx.method_name;
      
      if (methodName.includes('(')) {
        methodDisplayName = methodName.split('(')[0];
      } else {
        methodDisplayName = methodName;
      }
    } else if (tx.method_signature) {
      methodName = tx.method_signature;
      methodSignature = tx.method_signature;
      
      if (methodName.includes('(')) {
        methodDisplayName = methodName.split('(')[0];
      } else {
        methodDisplayName = methodName;
      }
    } else {
      if (tx.input && tx.input !== '0x') {
        methodName = 'Contract Call';
        methodDisplayName = 'Contract Call';
        if (tx.input.length >= 10) {
          methodSignature = tx.input.substring(0, 10);
        }
        
        if (!tx.to) {
          methodName = 'Contract Creation';
          methodDisplayName = 'Contract Creation';
        }
      } else {
        methodDisplayName = 'Transfer';
      }
    }
    
    if (methodDisplayName.toLowerCase() === 'transfer') {
      methodDisplayName = 'Transfer';
    } else if (methodDisplayName.toLowerCase() === 'approve') {
      methodDisplayName = 'Approve';
    } else if (methodDisplayName.toLowerCase() === 'mint') {
      methodDisplayName = 'Mint';
    } else if (methodDisplayName.toLowerCase() === 'withdraw') {
      methodDisplayName = 'Withdraw';
    } else if (methodDisplayName.toLowerCase() === 'deposit') {
      methodDisplayName = 'Deposit';
    } else if (methodDisplayName.toLowerCase() === 'swap') {
      methodDisplayName = 'Swap';
    }
    
    const nonceDecimal = parseHexToDecimal(tx.nonce);
    
    return {
      hash: tx.hash,
      from: tx.from,
      to: tx.to || 'Contract Creation',
      value: valueFormatted,
      status,
      statusCapitalized: capitalizedStatus,
      timestamp,
      age,
      method: methodName,
      methodDisplayName,
      methodSignature: methodSignature,
      txFee: txFee === '0' ? '0 tEXT' : `${txFee} tEXT`,
      txnFee: txFee === '0' ? '0 tEXT' : `${txFee} tEXT`,
      blockNumber: blockNumberFormatted,
      block: blockNumberValue,
      gas,
      gasPrice,
      gasUsed,
      nonce: tx.nonce || '0x0',
      input: tx.input || '0x',
      gasUsedDecimal: parseHexToDecimal(gasUsed),
      gasPriceGwei: parseHexToDecimal(gasPrice) / 1e9 > 0 ? (parseHexToDecimal(gasPrice) / 1e9).toFixed(2) : '0',
      gasDecimal: parseHexToDecimal(gas),
      nonceDecimal 
    };
  });
}


export interface PaginatedTransactions {
  transactions: Transaction[];
  totalCount: number;
  totalPages: number;
  page: number;
  perPage: number;
}

/**
 * @param {number} page 
 * @param {number} perPage 
 * @param {boolean} debug 
 * @returns {Promise<PaginatedTransactions>} 
 */
export async function getLatestTransactionsFromExplorer(
  page = 1,
  perPage = 10,
  debug = false
): Promise<PaginatedTransactions> {
  try {
    interface ExplorerTransactionsResponse {
      data?: any[];
      transactions?: any[];
      meta?: {
        total?: number;
        count?: number;
        total_pages?: number;
      };
      totalCount?: number;
      total?: number;
      count?: number;
      status?: string;
    }
    
    if (debug) logger.debug(`Fetching transactions: page=${page}, perPage=${perPage}`);

    const response = await callExplorerApi<ExplorerTransactionsResponse>('transactions', {
      limit: perPage,
      offset: (page - 1) * perPage
    });
    
    if (debug) {
      logger.debug('API Response Structure:', JSON.stringify(response).substring(0, 200) + '...');
      logger.debug('Response received, processing transactions...');
    }

    let txData: any[] = [];
    

    if (Array.isArray(response)) {

      txData = response;
    } else if (response.data && Array.isArray(response.data)) {
      txData = response.data;
    } else if (response.transactions && Array.isArray(response.transactions)) {
      txData = response.transactions;
    } else {
      logger.error('Unexpected API response format:', response);
      throw new Error('Unexpected API response format. Check logs for details.');
    }

    if (debug) logger.debug(`Processing ${txData.length} transactions...`);
    
    const transactions = txData.map((txData: any) => {
      const transaction: any = { 
        hash: txData.hash,
        blockNumber: txData.block_number 
          ? (txData.block_number.startsWith?.('0x') ? txData.block_number : `0x${parseInt(txData.block_number).toString(16)}`)
          : undefined,
        blockHash: txData.block_hash,
        from: txData.from_address || txData.from,
        to: txData.to_address || txData.to,
        value: txData.value 
          ? (txData.value.startsWith?.('0x') ? txData.value : `0x${BigInt(txData.value).toString(16)}`)
          : '0x0',
        gas: txData.gas 
          ? (txData.gas.startsWith?.('0x') ? txData.gas : `0x${parseInt(txData.gas).toString(16)}`)
          : undefined,
        gasPrice: txData.gas_price 
          ? (txData.gas_price.startsWith?.('0x') ? txData.gas_price : `0x${BigInt(txData.gas_price).toString(16)}`)
          : undefined,
        gasUsed: txData.gas_used 
          ? (txData.gas_used.startsWith?.('0x') ? txData.gas_used : `0x${parseInt(txData.gas_used).toString(16)}`)
          : undefined,
        nonce: txData.nonce 
          ? (txData.nonce.startsWith?.('0x') ? txData.nonce : `0x${parseInt(txData.nonce).toString(16)}`)
          : undefined,
        transactionIndex: txData.transaction_index 
          ? (txData.transaction_index.startsWith?.('0x') ? txData.transaction_index : `0x${parseInt(txData.transaction_index).toString(16)}`)
          : undefined,
        input: txData.input || txData.input_data || txData.data || '0x',
        status: txData.status,
        timestamp: standardizeTimestamp(txData.timestamp, true) || Date.now(),
        method_signature: txData.method_signature,
        method_name: txData.method_name
      };
      
      return transaction;
    });
    
    const formattedTransactions = formatTransactionsForDisplay(transactions);
    
    const totalCount = 
      (response.meta && response.meta.total) || 
      response.total || 
      response.totalCount || 
      response.count || 
      (response.meta && response.meta.count) || 
      transactions.length;
    
    const totalPages = 
      (response.meta && response.meta.total_pages) || 
      Math.ceil(totalCount / perPage);
    
    if (debug) logger.debug(`Returning ${transactions.length} transactions, totalCount: ${totalCount}, totalPages: ${totalPages}`);
    
    return {
      transactions: formattedTransactions,
      totalCount,
      totalPages,
      page,
      perPage
    };
  } catch (error: any) { 
    logger.error('Error fetching transactions from Explorer DB:', error);
    throw new Error(`Failed to get transactions from Explorer DB: ${error.message}`);
  }
}