const fs = require('fs');
const path = require('path');
const {
  JsonRpcProvider,
  Wallet,
  ZeroAddress,
  formatEther,
  getAddress,
  isAddress,
  parseEther,
  parseUnits
} = require('ethers');
const { EVM_CANDIDATE_CONFIG } = require('../config/evmCandidateConfig');

const dataPath = path.join(__dirname, '..', 'data', 'evm-transfers.json');
const sendQueues = new Map();

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

function provider() {
  return new JsonRpcProvider(EVM_CANDIDATE_CONFIG.rpcUrl, undefined, { staticNetwork: false });
}

function configuredGasPriceWei() {
  const value = String(EVM_CANDIDATE_CONFIG.minimumGasPriceGwei || '').trim();
  if (!/^\d+(\.\d{1,9})?$/.test(value)) {
    throw transferError('Invalid minimum gas price configuration.', 500, 'EVM_GAS_PRICE_INVALID');
  }
  return parseUnits(value, 'gwei');
}

function effectiveGasPrice(feeData) {
  const networkGasPrice = feeData.gasPrice || feeData.maxFeePerGas || 0n;
  const minimumGasPrice = configuredGasPriceWei();
  return networkGasPrice > minimumGasPrice ? networkGasPrice : minimumGasPrice;
}

function readStore() {
  if (!fs.existsSync(dataPath)) return { version: 1, transfers: [] };
  try {
    const parsed = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
    return { version: 1, transfers: Array.isArray(parsed.transfers) ? parsed.transfers : [] };
  } catch (error) {
    throw transferError('Transfer history could not be read.', 500, 'EVM_TRANSFER_STORE_INVALID');
  }
}

function writeStore(store) {
  fs.mkdirSync(path.dirname(dataPath), { recursive: true });
  const tempPath = `${dataPath}.tmp`;
  fs.writeFileSync(tempPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
  fs.renameSync(tempPath, dataPath);
}

function normalizeAmount(value) {
  const text = String(value || '').trim();
  if (!/^\d+(\.\d{1,18})?$/.test(text)) {
    throw transferError('Invalid transfer amount.', 400, 'INVALID_EVM_AMOUNT');
  }
  const wei = parseEther(text);
  if (wei <= 0n) throw transferError('Transfer amount must be positive.', 400, 'INVALID_EVM_AMOUNT');
  const minimumWei = parseEther(String(EVM_CANDIDATE_CONFIG.minimumTransferAmount));
  if (wei < minimumWei) {
    throw transferError('Transfer amount is below the service minimum.', 400, 'INVALID_EVM_AMOUNT');
  }
  return { text, wei };
}

function normalizeAddress(value) {
  const text = String(value || '').trim();
  if (!isAddress(text)) throw transferError('Invalid recipient address.', 400, 'INVALID_EVM_ADDRESS');
  const address = getAddress(text);
  if (address === ZeroAddress) throw transferError('Zero address is not allowed.', 400, 'INVALID_EVM_ADDRESS');
  return address;
}

async function assertNetwork(rpc) {
  const network = await rpc.getNetwork();
  if (Number(network.chainId) !== EVM_CANDIDATE_CONFIG.chainId) {
    throw transferError('Candidate network chain id mismatch.', 503, 'EVM_CHAIN_ID_MISMATCH');
  }
}

async function getAccountSummary(address) {
  if (!address || !isAddress(address)) return { balance: '0', balanceWei: '0', fee: null };
  const rpc = provider();
  await assertNetwork(rpc);
  const [balance, feeData] = await Promise.all([
    rpc.getBalance(address),
    rpc.getFeeData()
  ]);
  const gasPrice = effectiveGasPrice(feeData);
  const estimatedFeeWei = gasPrice * 21000n;
  return {
    balance: formatEther(balance),
    balanceWei: balance.toString(),
    gasPriceWei: gasPrice.toString(),
    estimatedFee: formatEther(estimatedFeeWei),
    estimatedFeeWei: estimatedFeeWei.toString()
  };
}

function addTransfer(record) {
  const store = readStore();
  store.transfers.unshift(record);
  store.transfers = store.transfers.slice(0, 5000);
  writeStore(store);
}

function updateTransfer(hash, patch) {
  const store = readStore();
  const row = store.transfers.find(item => item.hash === hash);
  if (!row) return;
  Object.assign(row, patch, { updatedAt: new Date().toISOString() });
  writeStore(store);
}

function listTransfers(address, limit = 30) {
  const safeAddress = String(address || '').toLowerCase();
  return readStore().transfers
    .filter(row => String(row.from || '').toLowerCase() === safeAddress || String(row.to || '').toLowerCase() === safeAddress)
    .slice(0, Math.max(1, Math.min(100, Number(limit) || 30)))
    .map(row => ({
      ...row,
      fee: row.feeWei ? formatEther(BigInt(row.feeWei)) : ''
    }));
}

function getStoredTransfer(hash) {
  const safeHash = String(hash || '').trim().toLowerCase();
  return readStore().transfers.find(row => String(row.hash || '').toLowerCase() === safeHash) || null;
}

function getStoredTransferByIdempotencyKey(idempotencyKey) {
  const key = String(idempotencyKey || '').trim();
  if (!key) return null;
  return readStore().transfers.find(row => String(row.idempotencyKey || '') === key) || null;
}

async function getTransactionDetails(hash) {
  const safeHash = String(hash || '').trim();
  if (!/^0x[0-9a-fA-F]{64}$/.test(safeHash)) return null;
  const rpc = provider();
  await assertNetwork(rpc);
  const [transaction, receipt] = await Promise.all([
    rpc.getTransaction(safeHash),
    rpc.getTransactionReceipt(safeHash)
  ]);
  const stored = getStoredTransfer(safeHash);
  if (!transaction && !stored) return null;
  return {
    hash: safeHash,
    from: transaction ? transaction.from : stored.from,
    to: transaction ? transaction.to : stored.to,
    amount: transaction ? formatEther(transaction.value) : stored.amount,
    nonce: transaction ? transaction.nonce : stored.nonce,
    blockNumber: receipt ? receipt.blockNumber : (stored.blockNumber || null),
    gasUsed: receipt ? receipt.gasUsed.toString() : (stored.gasUsed || ''),
    fee: receipt
      ? formatEther(receipt.gasUsed * (receipt.gasPrice || transaction?.gasPrice || 0n))
      : (stored.feeWei ? formatEther(BigInt(stored.feeWei)) : ''),
    status: receipt ? (Number(receipt.status) === 1 ? 'confirmed' : 'failed') : (stored.status || 'pending'),
    createdAt: stored ? stored.createdAt : '',
    memo: stored ? stored.memo : ''
  };
}

function enqueue(address, task) {
  const key = String(address).toLowerCase();
  const previous = sendQueues.get(key) || Promise.resolve();
  const current = previous.catch(() => {}).then(task);
  sendQueues.set(key, current);
  current.then(() => {
    if (sendQueues.get(key) === current) sendQueues.delete(key);
  }, () => {
    if (sendQueues.get(key) === current) sendQueues.delete(key);
  });
  return current;
}

async function sendSignedTransfer({ privateKey, to, amount, actorType, actorId, memo = '', idempotencyKey = '', onBroadcast = null }) {
  if (!EVM_CANDIDATE_CONFIG.externalTransfersEnabled) {
    throw transferError('Candidate network transfers are locked.', 503, 'EVM_TRANSFERS_LOCKED');
  }
  const recipient = normalizeAddress(to);
  const normalizedAmount = normalizeAmount(amount);
  const stableKey = String(idempotencyKey || '').trim();
  if (stableKey && !/^[A-Za-z0-9:_-]{12,220}$/.test(stableKey)) {
    throw transferError('Invalid transfer idempotency key.', 400, 'INVALID_EVM_IDEMPOTENCY_KEY');
  }
  if (!/^0x[0-9a-fA-F]{64}$/.test(String(privateKey || ''))) {
    throw transferError('Signing key is unavailable.', 503, 'EVM_SIGNER_UNAVAILABLE');
  }

  const rpc = provider();
  await assertNetwork(rpc);
  const signer = new Wallet(privateKey, rpc);
  return enqueue(signer.address, async () => {
    const existing = stableKey ? getStoredTransferByIdempotencyKey(stableKey) : null;
    if (existing) {
      const sameTransfer = String(existing.from || '').toLowerCase() === signer.address.toLowerCase()
        && String(existing.to || '').toLowerCase() === recipient.toLowerCase()
        && String(existing.amount || '') === normalizedAmount.text;
      if (!sameTransfer) {
        throw transferError('Idempotency key belongs to a different transfer.', 409, 'EVM_IDEMPOTENCY_CONFLICT');
      }
      const details = await getTransactionDetails(existing.hash);
      if (details?.status === 'failed') {
        throw transferError('The previous transaction failed.', 502, 'EVM_TRANSACTION_FAILED');
      }
      return {
        hash: existing.hash,
        from: existing.from,
        to: existing.to,
        amount: existing.amount,
        status: details?.status || existing.status,
        duplicate: true
      };
    }
    const [balance, feeData] = await Promise.all([rpc.getBalance(signer.address), rpc.getFeeData()]);
    const gasPrice = effectiveGasPrice(feeData);
    const estimatedFeeWei = gasPrice * 21000n;
    if (balance < normalizedAmount.wei + estimatedFeeWei) {
      throw transferError('Insufficient balance.', 400, 'INSUFFICIENT_EVM_BALANCE');
    }

    const tx = await signer.sendTransaction({
      to: recipient,
      value: normalizedAmount.wei,
      gasLimit: 21000n,
      gasPrice
    });
    const createdAt = new Date().toISOString();
    addTransfer({
      hash: tx.hash,
      from: signer.address,
      to: recipient,
      amount: normalizedAmount.text,
      valueWei: normalizedAmount.wei.toString(),
      status: 'pending',
      actorType: String(actorType || ''),
      actorId: String(actorId || ''),
      idempotencyKey: stableKey,
      memo: String(memo || '').slice(0, 200),
      chainId: EVM_CANDIDATE_CONFIG.chainId,
      nonce: tx.nonce,
      createdAt,
      updatedAt: createdAt
    });
    if (typeof onBroadcast === 'function') {
      await onBroadcast({ hash: tx.hash, from: signer.address, to: recipient, amount: normalizedAmount.text });
    }
    const receipt = await tx.wait(1);
    const status = receipt && Number(receipt.status) === 1 ? 'confirmed' : 'failed';
    updateTransfer(tx.hash, {
      status,
      blockNumber: receipt ? receipt.blockNumber : null,
      gasUsed: receipt ? receipt.gasUsed.toString() : '',
      feeWei: receipt ? (receipt.gasUsed * (receipt.gasPrice || tx.gasPrice || gasPrice)).toString() : '',
      confirmedAt: new Date().toISOString()
    });
    if (status !== 'confirmed') throw transferError('Transaction execution failed.', 502, 'EVM_TRANSACTION_FAILED');
    return { hash: tx.hash, receipt, from: signer.address, to: recipient, amount: normalizedAmount.text };
  });
}

module.exports = {
  getAccountSummary,
  getStoredTransfer,
  getStoredTransferByIdempotencyKey,
  getTransactionDetails,
  listTransfers,
  normalizeAddress,
  sendSignedTransfer,
  transferError
};
