const {
  Contract,
  JsonRpcProvider,
  Wallet,
  formatEther,
  formatUnits,
  getAddress,
  id,
  isAddress,
  parseUnits,
  zeroPadValue
} = require('ethers');
const fs = require('fs');
const path = require('path');
const { TronWeb } = require('tronweb');
const { MULTI_CHAIN_WALLET_CONFIG } = require('../config/multiChainWalletConfig');

const BABYDOGE_ABI = [
  'function balanceOf(address owner) view returns (uint256)',
  'function transfer(address to, uint256 amount) returns (bool)'
];
const TRANSFER_TOPIC = id('Transfer(address,address,uint256)');
const HISTORY_FILE = path.join(__dirname, '..', 'data', 'multichain-transfers.json');
const historyCache = new Map();

function readStoredTransfers() {
  try {
    const parsed = JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8'));
    return Array.isArray(parsed) ? parsed : [];
  } catch (error) {
    return [];
  }
}

function writeStoredTransfers(rows) {
  fs.mkdirSync(path.dirname(HISTORY_FILE), { recursive: true });
  const temporary = `${HISTORY_FILE}.tmp`;
  fs.writeFileSync(temporary, JSON.stringify(rows.slice(0, 5000), null, 2));
  fs.renameSync(temporary, HISTORY_FILE);
}

function explorerUrl(chain, hash) {
  if (chain === 'tron') return `${MULTI_CHAIN_WALLET_CONFIG.tron.explorerUrl}/#/transaction/${hash}`;
  return `${MULTI_CHAIN_WALLET_CONFIG.bsc.explorerUrl}/tx/${hash}`;
}

function recordTransfer(transfer) {
  if (!transfer || !transfer.hash || !transfer.asset) return;
  const row = {
    ...transfer,
    status: transfer.status || 'confirmed',
    createdAt: transfer.createdAt || new Date().toISOString(),
    explorerUrl: transfer.explorerUrl || explorerUrl(transfer.chain, transfer.hash)
  };
  const rows = readStoredTransfers().filter(item => String(item.hash).toLowerCase() !== String(row.hash).toLowerCase());
  rows.unshift(row);
  writeStoredTransfers(rows);
}

function directionFor(row, evmAddress, tronAddress) {
  const own = row.chain === 'tron' ? tronAddress : evmAddress;
  return String(row.from || '').toLowerCase() === String(own || '').toLowerCase() ? 'out' : 'in';
}

function mergeHistory(evmAddress, tronAddress, ...groups) {
  const unique = new Map();
  groups.flat().forEach(row => {
    if (!row || !row.hash || !row.asset) return;
    const normalized = {
      ...row,
      direction: row.direction || directionFor(row, evmAddress, tronAddress),
      explorerUrl: row.explorerUrl || explorerUrl(row.chain, row.hash)
    };
    unique.set(`${normalized.asset}:${String(normalized.hash).toLowerCase()}`, normalized);
  });
  return Array.from(unique.values())
    .sort((left, right) => new Date(right.createdAt || 0) - new Date(left.createdAt || 0))
    .slice(0, MULTI_CHAIN_WALLET_CONFIG.historyLimit);
}

function cached(key, loader) {
  const current = historyCache.get(key);
  if (current && current.expiresAt > Date.now()) return current.value;
  const value = Promise.resolve().then(loader).catch(() => []);
  historyCache.set(key, { expiresAt: Date.now() + MULTI_CHAIN_WALLET_CONFIG.historyCacheMs, value });
  return value;
}

function chainError(message, code, status = 400) {
  const error = new Error(message);
  error.code = code;
  error.status = status;
  return error;
}

function withTimeout(promise, label) {
  let timer;
  const timeout = new Promise((resolve, reject) => {
    timer = setTimeout(() => reject(chainError(`${label} request timed out.`, 'MULTICHAIN_TIMEOUT', 504)), MULTI_CHAIN_WALLET_CONFIG.requestTimeoutMs);
  });
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}

function createBscProvider() {
  return new JsonRpcProvider(MULTI_CHAIN_WALLET_CONFIG.bsc.rpcUrl, MULTI_CHAIN_WALLET_CONFIG.bsc.chainId, {
    staticNetwork: true
  });
}

function createTronWeb(privateKey) {
  const options = {
    fullHost: MULTI_CHAIN_WALLET_CONFIG.tron.apiBase,
    headers: MULTI_CHAIN_WALLET_CONFIG.tron.apiKey
      ? { 'TRON-PRO-API-KEY': MULTI_CHAIN_WALLET_CONFIG.tron.apiKey }
      : {}
  };
  if (privateKey) options.privateKey = String(privateKey).replace(/^0x/, '');
  return new TronWeb(options);
}

function deriveTronAddress(evmAddress) {
  if (!isAddress(String(evmAddress || '').trim())) {
    throw chainError('A valid EVM address is required.', 'INVALID_EVM_ADDRESS');
  }
  return TronWeb.address.fromHex(`41${getAddress(evmAddress).slice(2)}`);
}

function validateDecimalAmount(value, decimals, minimum, code) {
  const text = String(value || '').trim();
  if (!/^\d+(?:\.\d+)?$/.test(text)) throw chainError('A valid transfer amount is required.', code);
  const fraction = text.includes('.') ? text.split('.')[1] : '';
  if (fraction.length > decimals) throw chainError(`Amount supports up to ${decimals} decimal places.`, code);
  const raw = parseUnits(text, decimals);
  const minimumRaw = parseUnits(String(minimum), decimals);
  if (raw < minimumRaw) throw chainError(`Minimum transfer is ${minimum}.`, code);
  return { text, raw };
}

async function getBscAssets(evmAddress) {
  const address = getAddress(evmAddress);
  const provider = createBscProvider();
  const token = new Contract(MULTI_CHAIN_WALLET_CONFIG.babyDoge.contractAddress, BABYDOGE_ABI, provider);
  try {
    const [network, bnbRaw, tokenRaw, blockNumber] = await withTimeout(Promise.all([
      provider.getNetwork(),
      provider.getBalance(address),
      token.balanceOf(address),
      provider.getBlockNumber()
    ]), 'BSC');
    if (Number(network.chainId) !== MULTI_CHAIN_WALLET_CONFIG.bsc.chainId) {
      throw chainError('BSC RPC chain ID mismatch.', 'BSC_CHAIN_ID_MISMATCH', 502);
    }
    return {
      online: true,
      address,
      bnbBalance: formatEther(bnbRaw),
      babyDogeBalance: formatUnits(tokenRaw, MULTI_CHAIN_WALLET_CONFIG.babyDoge.decimals),
      blockNumber,
      error: ''
    };
  } catch (error) {
    return { online: false, address, bnbBalance: '-', babyDogeBalance: '-', blockNumber: null, error: error.code || 'BSC_UNAVAILABLE' };
  }
}

async function getTronAsset(evmAddress) {
  const address = deriveTronAddress(evmAddress);
  const tronWeb = createTronWeb();
  try {
    const [balanceSun, account] = await withTimeout(Promise.all([
      tronWeb.trx.getBalance(address),
      tronWeb.trx.getAccount(address).catch(() => ({}))
    ]), 'TRON');
    return {
      online: true,
      address,
      balance: formatUnits(BigInt(balanceSun || 0), MULTI_CHAIN_WALLET_CONFIG.tron.decimals),
      activated: Boolean(account && account.address),
      error: ''
    };
  } catch (error) {
    return { online: false, address, balance: '-', activated: false, error: error.code || 'TRON_UNAVAILABLE' };
  }
}

async function getPortfolio(evmAddress) {
  if (!evmAddress) return null;
  const [bsc, tron] = await Promise.all([getBscAssets(evmAddress), getTronAsset(evmAddress)]);
  const transactions = await getTransactionHistory(bsc.address, tron.address);
  return { bsc, tron, transactions, config: MULTI_CHAIN_WALLET_CONFIG };
}

async function getTrxHistory(tronAddress) {
  return cached(`trx:${tronAddress}`, async () => {
    const headers = MULTI_CHAIN_WALLET_CONFIG.tron.apiKey
      ? { 'TRON-PRO-API-KEY': MULTI_CHAIN_WALLET_CONFIG.tron.apiKey }
      : {};
    const url = new URL(`${MULTI_CHAIN_WALLET_CONFIG.tron.apiBase}/v1/accounts/${encodeURIComponent(tronAddress)}/transactions`);
    url.searchParams.set('only_confirmed', 'true');
    url.searchParams.set('limit', String(MULTI_CHAIN_WALLET_CONFIG.historyLimit));
    url.searchParams.set('order_by', 'block_timestamp,desc');
    const response = await withTimeout(fetch(url, { headers }), 'TRON history');
    if (!response.ok) return [];
    const payload = await response.json();
    return (Array.isArray(payload.data) ? payload.data : []).flatMap(transaction => {
      const contract = transaction.raw_data && transaction.raw_data.contract && transaction.raw_data.contract[0];
      if (!contract || contract.type !== 'TransferContract') return [];
      const value = contract.parameter && contract.parameter.value;
      if (!value || value.amount == null) return [];
      try {
        return [{
          chain: 'tron',
          asset: 'TRX',
          hash: transaction.txID,
          blockNumber: transaction.blockNumber || null,
          from: TronWeb.address.fromHex(value.owner_address),
          to: TronWeb.address.fromHex(value.to_address),
          amount: formatUnits(BigInt(value.amount), MULTI_CHAIN_WALLET_CONFIG.tron.decimals),
          fee: transaction.ret && transaction.ret[0] && transaction.ret[0].fee
            ? formatUnits(BigInt(transaction.ret[0].fee), MULTI_CHAIN_WALLET_CONFIG.tron.decimals)
            : '',
          status: transaction.ret && transaction.ret[0] && transaction.ret[0].contractRet === 'SUCCESS' ? 'confirmed' : 'failed',
          createdAt: new Date(Number(transaction.block_timestamp || Date.now())).toISOString()
        }];
      } catch (error) {
        return [];
      }
    });
  });
}

async function getBabyDogeHistory(evmAddress) {
  return cached(`babydoge:${evmAddress}`, async () => {
    const provider = createBscProvider();
    const latest = await withTimeout(provider.getBlockNumber(), 'BSC history');
    const fromBlock = Math.max(0, latest - MULTI_CHAIN_WALLET_CONFIG.bscHistoryBlocks);
    const addressTopic = zeroPadValue(evmAddress, 32);
    const ranges = [];
    for (let start = fromBlock; start <= latest; start += 5000) {
      ranges.push({ fromBlock: start, toBlock: Math.min(latest, start + 4999) });
    }
    const resultGroups = await withTimeout(Promise.all(ranges.flatMap(range => {
      const filterBase = { address: MULTI_CHAIN_WALLET_CONFIG.babyDoge.contractAddress, ...range };
      return [
        provider.getLogs({ ...filterBase, topics: [TRANSFER_TOPIC, addressTopic] }),
        provider.getLogs({ ...filterBase, topics: [TRANSFER_TOPIC, null, addressTopic] })
      ];
    })), 'BSC token history');
    const sent = resultGroups.filter((value, index) => index % 2 === 0).flat();
    const received = resultGroups.filter((value, index) => index % 2 === 1).flat();
    const logs = Array.from(new Map([...sent, ...received].map(log => [`${log.transactionHash}:${log.index}`, log])).values())
      .sort((left, right) => right.blockNumber - left.blockNumber)
      .slice(0, MULTI_CHAIN_WALLET_CONFIG.historyLimit);
    const blockTimes = new Map();
    await Promise.all(Array.from(new Set(logs.map(log => log.blockNumber))).map(async blockNumber => {
      const block = await provider.getBlock(blockNumber);
      blockTimes.set(blockNumber, block ? Number(block.timestamp) * 1000 : Date.now());
    }));
    return logs.map(log => ({
      chain: 'bsc',
      asset: 'BABYDOGE',
      hash: log.transactionHash,
      blockNumber: log.blockNumber,
      from: getAddress(`0x${log.topics[1].slice(-40)}`),
      to: getAddress(`0x${log.topics[2].slice(-40)}`),
      amount: formatUnits(BigInt(log.data), MULTI_CHAIN_WALLET_CONFIG.babyDoge.decimals),
      fee: '',
      status: 'confirmed',
      createdAt: new Date(blockTimes.get(log.blockNumber) || Date.now()).toISOString()
    }));
  });
}

async function getTransactionHistory(evmAddress, tronAddress) {
  const stored = readStoredTransfers().filter(row => {
    const ownAddress = row.chain === 'tron' ? tronAddress : evmAddress;
    return [row.from, row.to].some(value => String(value || '').toLowerCase() === String(ownAddress || '').toLowerCase());
  });
  const [babyDoge, trx] = await Promise.all([
    getBabyDogeHistory(evmAddress),
    getTrxHistory(tronAddress)
  ]);
  return {
    bnb: mergeHistory(evmAddress, tronAddress, stored.filter(row => row.asset === 'BNB')),
    babyDoge: mergeHistory(evmAddress, tronAddress, stored.filter(row => row.asset === 'BABYDOGE'), babyDoge),
    trx: mergeHistory(evmAddress, tronAddress, stored.filter(row => row.asset === 'TRX'), trx),
    bscAddressUrl: `${MULTI_CHAIN_WALLET_CONFIG.bsc.explorerUrl}/address/${evmAddress}`,
    tronAddressUrl: `${MULTI_CHAIN_WALLET_CONFIG.tron.explorerUrl}/#/address/${tronAddress}`
  };
}

async function sendBabyDoge({ privateKey, to, amount } = {}) {
  if (!MULTI_CHAIN_WALLET_CONFIG.transfersEnabled) throw chainError('Multichain transfers are disabled.', 'MULTICHAIN_TRANSFERS_DISABLED', 503);
  if (!isAddress(String(to || '').trim())) throw chainError('Invalid BSC recipient address.', 'INVALID_BSC_ADDRESS');
  const parsed = validateDecimalAmount(amount, MULTI_CHAIN_WALLET_CONFIG.babyDoge.decimals, MULTI_CHAIN_WALLET_CONFIG.babyDoge.minimumTransferAmount, 'INVALID_BABYDOGE_AMOUNT');
  const provider = createBscProvider();
  const signer = new Wallet(privateKey, provider);
  const token = new Contract(MULTI_CHAIN_WALLET_CONFIG.babyDoge.contractAddress, BABYDOGE_ABI, signer);
  const [network, tokenBalance, bnbBalance] = await withTimeout(Promise.all([
    provider.getNetwork(), token.balanceOf(signer.address), provider.getBalance(signer.address)
  ]), 'BSC');
  if (Number(network.chainId) !== MULTI_CHAIN_WALLET_CONFIG.bsc.chainId) throw chainError('BSC RPC chain ID mismatch.', 'BSC_CHAIN_ID_MISMATCH', 502);
  if (tokenBalance < parsed.raw) throw chainError('Insufficient BABYDOGE balance.', 'INSUFFICIENT_BABYDOGE_BALANCE');
  const recipient = getAddress(to);
  const gasLimit = await token.transfer.estimateGas(recipient, parsed.raw);
  const feeData = await provider.getFeeData();
  const gasPrice = feeData.gasPrice || feeData.maxFeePerGas;
  if (!gasPrice || bnbBalance < gasLimit * gasPrice) throw chainError('Insufficient BNB for network fee.', 'INSUFFICIENT_BNB_GAS');
  const transaction = await token.transfer(recipient, parsed.raw, { gasLimit: gasLimit * 120n / 100n });
  const receipt = await transaction.wait(1);
  return { chain: 'bsc', asset: 'BABYDOGE', hash: transaction.hash, blockNumber: receipt.blockNumber, from: signer.address, to: recipient, amount: parsed.text, fee: formatEther(receipt.gasUsed * receipt.gasPrice), status: receipt.status === 1 ? 'confirmed' : 'failed' };
}

async function sendBnb({ privateKey, to, amount } = {}) {
  if (!MULTI_CHAIN_WALLET_CONFIG.transfersEnabled) throw chainError('Multichain transfers are disabled.', 'MULTICHAIN_TRANSFERS_DISABLED', 503);
  if (!isAddress(String(to || '').trim())) throw chainError('Invalid BSC recipient address.', 'INVALID_BSC_ADDRESS');
  const parsed = validateDecimalAmount(amount, MULTI_CHAIN_WALLET_CONFIG.bsc.decimals, MULTI_CHAIN_WALLET_CONFIG.bsc.minimumTransferAmount, 'INVALID_BNB_AMOUNT');
  const provider = createBscProvider();
  const signer = new Wallet(privateKey, provider);
  const recipient = getAddress(to);
  const [network, balance] = await withTimeout(Promise.all([
    provider.getNetwork(), provider.getBalance(signer.address)
  ]), 'BSC');
  if (Number(network.chainId) !== MULTI_CHAIN_WALLET_CONFIG.bsc.chainId) throw chainError('BSC RPC chain ID mismatch.', 'BSC_CHAIN_ID_MISMATCH', 502);
  const gasLimit = await provider.estimateGas({ from: signer.address, to: recipient, value: parsed.raw });
  const paddedGasLimit = gasLimit * 120n / 100n;
  const feeData = await provider.getFeeData();
  const gasPrice = feeData.gasPrice || feeData.maxFeePerGas;
  if (!gasPrice || balance < parsed.raw + (paddedGasLimit * gasPrice)) throw chainError('Insufficient BNB balance including network fee.', 'INSUFFICIENT_BNB_BALANCE');
  const transaction = await signer.sendTransaction({ to: recipient, value: parsed.raw, gasLimit: paddedGasLimit });
  const receipt = await transaction.wait(1);
  return { chain: 'bsc', asset: 'BNB', hash: transaction.hash, blockNumber: receipt.blockNumber, from: signer.address, to: recipient, amount: parsed.text, fee: formatEther(receipt.gasUsed * receipt.gasPrice), status: receipt.status === 1 ? 'confirmed' : 'failed' };
}

async function sendTrx({ privateKey, to, amount } = {}) {
  if (!MULTI_CHAIN_WALLET_CONFIG.transfersEnabled) throw chainError('Multichain transfers are disabled.', 'MULTICHAIN_TRANSFERS_DISABLED', 503);
  const tronWeb = createTronWeb(privateKey);
  if (!TronWeb.isAddress(String(to || '').trim())) throw chainError('Invalid TRON recipient address.', 'INVALID_TRON_ADDRESS');
  const parsed = validateDecimalAmount(amount, MULTI_CHAIN_WALLET_CONFIG.tron.decimals, MULTI_CHAIN_WALLET_CONFIG.tron.minimumTransferAmount, 'INVALID_TRX_AMOUNT');
  const amountSun = Number(parsed.raw);
  if (!Number.isSafeInteger(amountSun)) throw chainError('TRX amount is too large.', 'INVALID_TRX_AMOUNT');
  const sender = tronWeb.defaultAddress.base58;
  const balanceSun = await withTimeout(tronWeb.trx.getBalance(sender), 'TRON');
  if (BigInt(balanceSun || 0) < parsed.raw) throw chainError('Insufficient TRX balance.', 'INSUFFICIENT_TRX_BALANCE');
  const unsigned = await tronWeb.transactionBuilder.sendTrx(String(to).trim(), amountSun, sender);
  const signed = await tronWeb.trx.sign(unsigned, String(privateKey).replace(/^0x/, ''));
  const result = await tronWeb.trx.sendRawTransaction(signed);
  if (!result || result.result !== true) throw chainError('TRON broadcast was rejected.', 'TRX_BROADCAST_FAILED', 502);
  return { chain: 'tron', asset: 'TRX', hash: result.txid || signed.txID, from: sender, to: String(to).trim(), amount: parsed.text, status: 'pending' };
}

module.exports = {
  deriveTronAddress,
  getPortfolio,
  getTransactionHistory,
  recordTransfer,
  sendBnb,
  sendBabyDoge,
  sendTrx,
  validateDecimalAmount
};
