import log from 'loglevel';
import BigNumber from 'bignumber.js';
import abi from 'human-standard-token-abi';
import {
  SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
  ALLOWED_CONTRACT_ADDRESSES,
  SWAPS_WRAPPED_TOKENS_ADDRESSES,
  ETHEREUM,
  POLYGON,
  BSC,
  RINKEBY,
  AVALANCHE,
  SWAPS_API_V2_BASE_URL,
  SWAPS_DEV_API_V2_BASE_URL,
  GAS_API_BASE_URL,
  GAS_DEV_API_BASE_URL,
  SWAPS_CLIENT_ID,
} from '../../../shared/constants/swaps';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
import {
  isSwapsDefaultTokenAddress,
  isSwapsDefaultTokenSymbol,
} from '../../../shared/modules/swaps.utils';
import {
  MAINNET_CHAIN_ID,
  BSC_CHAIN_ID,
  POLYGON_CHAIN_ID,
  LOCALHOST_CHAIN_ID,
  RINKEBY_CHAIN_ID,
  ETH_SYMBOL,
  AVALANCHE_CHAIN_ID,
} from '../../../shared/constants/network';
import { SECOND } from '../../../shared/constants/time';
import {
  calcTokenValue,
  calcTokenAmount,
} from '../../helpers/utils/token-util';
import {
  constructTxParams,
  toPrecisionWithoutTrailingZeros,
} from '../../helpers/utils/util';
import {
  decimalToHex,
  getValueFromWeiHex,
} from '../../helpers/utils/conversions.util';

import { subtractCurrencies } from '../../../shared/modules/conversion.utils';
import { formatCurrency } from '../../helpers/utils/confirm-tx.util';
import fetchWithCache from '../../helpers/utils/fetch-with-cache';

import { calcGasTotal } from '../send/send.utils';
import { isValidHexAddress } from '../../../shared/modules/hexstring-utils';

const TOKEN_TRANSFER_LOG_TOPIC_HASH =
  '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';

const CACHE_REFRESH_FIVE_MINUTES = 300000;

const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID };

/**
 * @param {string} type - Type of an API call, e.g. "tokens"
 * @param {string} chainId
 * @returns string
 */
const getBaseUrlForNewSwapsApi = (type, chainId) => {
  const useDevApis = process.env.SWAPS_USE_DEV_APIS;
  const v2ApiBaseUrl = useDevApis
    ? SWAPS_DEV_API_V2_BASE_URL
    : SWAPS_API_V2_BASE_URL;
  const gasApiBaseUrl = useDevApis ? GAS_DEV_API_BASE_URL : GAS_API_BASE_URL;
  const noNetworkSpecificTypes = ['refreshTime']; // These types don't need network info in the URL.
  if (noNetworkSpecificTypes.includes(type)) {
    return v2ApiBaseUrl;
  }
  const chainIdDecimal = chainId && parseInt(chainId, 16);
  const gasApiTypes = ['gasPrices'];
  if (gasApiTypes.includes(type)) {
    return `${gasApiBaseUrl}/networks/${chainIdDecimal}`; // Gas calculations are in its own repo.
  }
  return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`;
};

const TEST_CHAIN_IDS = [RINKEBY_CHAIN_ID, LOCALHOST_CHAIN_ID];

export const getBaseApi = function (type, chainId = MAINNET_CHAIN_ID) {
  // eslint-disable-next-line no-param-reassign
  chainId = TEST_CHAIN_IDS.includes(chainId) ? MAINNET_CHAIN_ID : chainId;
  const baseUrl = getBaseUrlForNewSwapsApi(type, chainId);
  const chainIdDecimal = chainId && parseInt(chainId, 16);
  if (!baseUrl) {
    throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
  }
  switch (type) {
    case 'trade':
      return `${baseUrl}/trades?`;
    case 'tokens':
      return `${baseUrl}/tokens`;
    case 'token':
      return `${baseUrl}/token`;
    case 'topAssets':
      return `${baseUrl}/topAssets`;
    case 'aggregatorMetadata':
      return `${baseUrl}/aggregatorMetadata`;
    case 'gasPrices':
      return `${baseUrl}/gasPrices`;
    case 'network':
      // Only use v2 for this endpoint.
      return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`;
    default:
      throw new Error('getBaseApi requires an api call type');
  }
};

const validHex = (string) => Boolean(string?.match(/^0x[a-f0-9]+$/u));
const truthyString = (string) => Boolean(string?.length);
const truthyDigitString = (string) =>
  truthyString(string) && Boolean(string.match(/^\d+$/u));

const QUOTE_VALIDATORS = [
  {
    property: 'trade',
    type: 'object',
    validator: (trade) =>
      trade &&
      validHex(trade.data) &&
      isValidHexAddress(trade.to, { allowNonPrefixed: false }) &&
      isValidHexAddress(trade.from, { allowNonPrefixed: false }) &&
      truthyString(trade.value),
  },
  {
    property: 'approvalNeeded',
    type: 'object',
    validator: (approvalTx) =>
      approvalTx === null ||
      (approvalTx &&
        validHex(approvalTx.data) &&
        isValidHexAddress(approvalTx.to, { allowNonPrefixed: false }) &&
        isValidHexAddress(approvalTx.from, { allowNonPrefixed: false })),
  },
  {
    property: 'sourceAmount',
    type: 'string',
    validator: truthyDigitString,
  },
  {
    property: 'destinationAmount',
    type: 'string',
    validator: truthyDigitString,
  },
  {
    property: 'sourceToken',
    type: 'string',
    validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }),
  },
  {
    property: 'destinationToken',
    type: 'string',
    validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }),
  },
  {
    property: 'aggregator',
    type: 'string',
    validator: truthyString,
  },
  {
    property: 'aggType',
    type: 'string',
    validator: truthyString,
  },
  {
    property: 'error',
    type: 'object',
    validator: (error) => error === null || typeof error === 'object',
  },
  {
    property: 'averageGas',
    type: 'number',
  },
  {
    property: 'maxGas',
    type: 'number',
  },
  {
    property: 'gasEstimate',
    type: 'number|undefined',
    validator: (gasEstimate) => gasEstimate === undefined || gasEstimate > 0,
  },
  {
    property: 'fee',
    type: 'number',
  },
];

const TOKEN_VALIDATORS = [
  {
    property: 'address',
    type: 'string',
    validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }),
  },
  {
    property: 'symbol',
    type: 'string',
    validator: (string) => truthyString(string) && string.length <= 12,
  },
  {
    property: 'decimals',
    type: 'string|number',
    validator: (string) => Number(string) >= 0 && Number(string) <= 36,
  },
];

const TOP_ASSET_VALIDATORS = TOKEN_VALIDATORS.slice(0, 2);

const AGGREGATOR_METADATA_VALIDATORS = [
  {
    property: 'color',
    type: 'string',
    validator: (string) => Boolean(string.match(/^#[A-Fa-f0-9]+$/u)),
  },
  {
    property: 'title',
    type: 'string',
    validator: truthyString,
  },
  {
    property: 'icon',
    type: 'string',
    validator: (string) => Boolean(string.match(/^data:image/u)),
  },
];

const isValidDecimalNumber = (string) =>
  !isNaN(string) && string.match(/^[.0-9]+$/u) && !isNaN(parseFloat(string));

const SWAP_GAS_PRICE_VALIDATOR = [
  {
    property: 'SafeGasPrice',
    type: 'string',
    validator: isValidDecimalNumber,
  },
  {
    property: 'ProposeGasPrice',
    type: 'string',
    validator: isValidDecimalNumber,
  },
  {
    property: 'FastGasPrice',
    type: 'string',
    validator: isValidDecimalNumber,
  },
];

function validateData(validators, object, urlUsed) {
  return validators.every(({ property, type, validator }) => {
    const types = type.split('|');

    const valid =
      types.some((_type) => typeof object[property] === _type) &&
      (!validator || validator(object[property]));
    if (!valid) {
      log.error(
        `response to GET ${urlUsed} invalid for property ${property}; value was:`,
        object[property],
        '| type was: ',
        typeof object[property],
      );
    }
    return valid;
  });
}

export const shouldEnableDirectWrapping = (
  chainId,
  sourceToken,
  destinationToken,
) => {
  if (!sourceToken || !destinationToken) {
    return false;
  }
  const wrappedToken = SWAPS_WRAPPED_TOKENS_ADDRESSES[chainId];
  const nativeToken = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.address;
  const sourceTokenLowerCase = sourceToken.toLowerCase();
  const destinationTokenLowerCase = destinationToken.toLowerCase();
  return (
    (sourceTokenLowerCase === wrappedToken &&
      destinationTokenLowerCase === nativeToken) ||
    (sourceTokenLowerCase === nativeToken &&
      destinationTokenLowerCase === wrappedToken)
  );
};

export async function fetchTradesInfo(
  {
    slippage,
    sourceToken,
    sourceDecimals,
    destinationToken,
    value,
    fromAddress,
    exchangeList,
  },
  { chainId },
) {
  const urlParams = {
    destinationToken,
    sourceToken,
    sourceAmount: calcTokenValue(value, sourceDecimals).toString(10),
    slippage,
    timeout: SECOND * 10,
    walletAddress: fromAddress,
  };

  if (exchangeList) {
    urlParams.exchangeList = exchangeList;
  }
  if (shouldEnableDirectWrapping(chainId, sourceToken, destinationToken)) {
    urlParams.enableDirectWrapping = true;
  }

  const queryString = new URLSearchParams(urlParams).toString();
  const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`;
  const tradesResponse = await fetchWithCache(
    tradeURL,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: 0, timeout: SECOND * 15 },
  );
  const newQuotes = tradesResponse.reduce((aggIdTradeMap, quote) => {
    if (
      quote.trade &&
      !quote.error &&
      validateData(QUOTE_VALIDATORS, quote, tradeURL)
    ) {
      const constructedTrade = constructTxParams({
        to: quote.trade.to,
        from: quote.trade.from,
        data: quote.trade.data,
        amount: decimalToHex(quote.trade.value),
        gas: decimalToHex(quote.maxGas),
      });

      let { approvalNeeded } = quote;

      if (approvalNeeded) {
        approvalNeeded = constructTxParams({
          ...approvalNeeded,
        });
      }

      return {
        ...aggIdTradeMap,
        [quote.aggregator]: {
          ...quote,
          slippage,
          trade: constructedTrade,
          approvalNeeded,
        },
      };
    }
    return aggIdTradeMap;
  }, {});

  return newQuotes;
}

export async function fetchToken(contractAddress, chainId) {
  const tokenUrl = getBaseApi('token', chainId);
  const token = await fetchWithCache(
    `${tokenUrl}?address=${contractAddress}`,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
  );
  return token;
}

export async function fetchTokens(chainId) {
  const tokensUrl = getBaseApi('tokens', chainId);
  const tokens = await fetchWithCache(
    tokensUrl,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
  );
  const filteredTokens = [
    SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId],
    ...tokens.filter((token) => {
      return (
        validateData(TOKEN_VALIDATORS, token, tokensUrl) &&
        !(
          isSwapsDefaultTokenSymbol(token.symbol, chainId) ||
          isSwapsDefaultTokenAddress(token.address, chainId)
        )
      );
    }),
  ];
  return filteredTokens;
}

export async function fetchAggregatorMetadata(chainId) {
  const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
  const aggregators = await fetchWithCache(
    aggregatorMetadataUrl,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
  );
  const filteredAggregators = {};
  for (const aggKey in aggregators) {
    if (
      validateData(
        AGGREGATOR_METADATA_VALIDATORS,
        aggregators[aggKey],
        aggregatorMetadataUrl,
      )
    ) {
      filteredAggregators[aggKey] = aggregators[aggKey];
    }
  }
  return filteredAggregators;
}

export async function fetchTopAssets(chainId) {
  const topAssetsUrl = getBaseApi('topAssets', chainId);
  const response = await fetchWithCache(
    topAssetsUrl,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
  );
  const topAssetsMap = response.reduce((_topAssetsMap, asset, index) => {
    if (validateData(TOP_ASSET_VALIDATORS, asset, topAssetsUrl)) {
      return { ..._topAssetsMap, [asset.address]: { index: String(index) } };
    }
    return _topAssetsMap;
  }, {});
  return topAssetsMap;
}

export async function fetchSwapsFeatureFlags() {
  const v2ApiBaseUrl = process.env.SWAPS_USE_DEV_APIS
    ? SWAPS_DEV_API_V2_BASE_URL
    : SWAPS_API_V2_BASE_URL;
  const response = await fetchWithCache(
    `${v2ApiBaseUrl}/featureFlags`,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: 600000 },
  );
  return response;
}

export async function fetchTokenPrice(address) {
  const query = `contract_addresses=${address}&vs_currencies=eth`;

  const prices = await fetchWithCache(
    `https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
    { method: 'GET' },
    { cacheRefreshTime: 60000 },
  );
  return prices && prices[address]?.eth;
}

export async function fetchTokenBalance(address, userAddress) {
  const tokenContract = global.eth.contract(abi).at(address);
  const tokenBalancePromise = tokenContract
    ? tokenContract.balanceOf(userAddress)
    : Promise.resolve();
  const usersToken = await tokenBalancePromise;
  return usersToken;
}

export async function fetchSwapsGasPrices(chainId) {
  const gasPricesUrl = getBaseApi('gasPrices', chainId);
  const response = await fetchWithCache(
    gasPricesUrl,
    { method: 'GET', headers: clientIdHeader },
    { cacheRefreshTime: 30000 },
  );
  const responseIsValid = validateData(
    SWAP_GAS_PRICE_VALIDATOR,
    response,
    gasPricesUrl,
  );

  if (!responseIsValid) {
    throw new Error(`${gasPricesUrl} response is invalid`);
  }

  const {
    SafeGasPrice: safeLow,
    ProposeGasPrice: average,
    FastGasPrice: fast,
  } = response;

  return {
    safeLow,
    average,
    fast,
  };
}

export const getFeeForSmartTransaction = ({
  chainId,
  currentCurrency,
  conversionRate,
  nativeCurrencySymbol,
  feeInWeiDec,
}) => {
  const feeInWeiHex = decimalToHex(feeInWeiDec);
  const ethFee = getValueFromWeiHex({
    value: feeInWeiHex,
    toDenomination: ETH_SYMBOL,
    numberOfDecimals: 5,
  });
  const rawNetworkFees = getValueFromWeiHex({
    value: feeInWeiHex,
    toCurrency: currentCurrency,
    conversionRate,
    numberOfDecimals: 2,
  });
  const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency);
  const chainCurrencySymbolToUse =
    nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol;
  return {
    feeInFiat: formattedNetworkFee,
    feeInEth: `${ethFee} ${chainCurrencySymbolToUse}`,
  };
};

export function getRenderableNetworkFeesForQuote({
  tradeGas,
  approveGas,
  gasPrice,
  currentCurrency,
  conversionRate,
  tradeValue,
  sourceSymbol,
  sourceAmount,
  chainId,
  nativeCurrencySymbol,
}) {
  const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
    .plus(approveGas || '0x0', 16)
    .toString(16);
  const gasTotalInWeiHex = calcGasTotal(totalGasLimitForCalculation, gasPrice);

  const nonGasFee = new BigNumber(tradeValue, 16)
    .minus(
      isSwapsDefaultTokenSymbol(sourceSymbol, chainId) ? sourceAmount : 0,
      10,
    )
    .toString(16);

  const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
    .plus(nonGasFee, 16)
    .toString(16);

  const ethFee = getValueFromWeiHex({
    value: totalWeiCost,
    toDenomination: 'ETH',
    numberOfDecimals: 5,
  });
  const rawNetworkFees = getValueFromWeiHex({
    value: totalWeiCost,
    toCurrency: currentCurrency,
    conversionRate,
    numberOfDecimals: 2,
  });
  const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency);

  const chainCurrencySymbolToUse =
    nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol;

  return {
    rawNetworkFees,
    rawEthFee: ethFee,
    feeInFiat: formattedNetworkFee,
    feeInEth: `${ethFee} ${chainCurrencySymbolToUse}`,
    nonGasFee,
  };
}

export function quotesToRenderableData(
  quotes,
  gasPrice,
  conversionRate,
  currentCurrency,
  approveGas,
  tokenConversionRates,
  chainId,
  smartTransactionEstimatedGas,
  nativeCurrencySymbol,
) {
  return Object.values(quotes).map((quote) => {
    const {
      destinationAmount = 0,
      sourceAmount = 0,
      sourceTokenInfo,
      destinationTokenInfo,
      slippage,
      aggType,
      aggregator,
      gasEstimateWithRefund,
      averageGas,
      fee,
      trade,
    } = quote;
    const sourceValue = calcTokenAmount(
      sourceAmount,
      sourceTokenInfo.decimals,
    ).toString(10);
    const destinationValue = calcTokenAmount(
      destinationAmount,
      destinationTokenInfo.decimals,
    ).toPrecision(8);

    let feeInFiat = null;
    let feeInEth = null;
    let rawNetworkFees = null;
    let rawEthFee = null;

    ({
      feeInFiat,
      feeInEth,
      rawNetworkFees,
      rawEthFee,
    } = getRenderableNetworkFeesForQuote({
      tradeGas: gasEstimateWithRefund || decimalToHex(averageGas || 800000),
      approveGas,
      gasPrice,
      currentCurrency,
      conversionRate,
      tradeValue: trade.value,
      sourceSymbol: sourceTokenInfo.symbol,
      sourceAmount,
      chainId,
    }));

    if (smartTransactionEstimatedGas) {
      ({ feeInFiat, feeInEth } = getFeeForSmartTransaction({
        chainId,
        currentCurrency,
        conversionRate,
        nativeCurrencySymbol,
        estimatedFeeInWeiDec: smartTransactionEstimatedGas.feeEstimate,
      }));
    }

    const slippageMultiplier = new BigNumber(100 - slippage).div(100);
    const minimumAmountReceived = new BigNumber(destinationValue)
      .times(slippageMultiplier)
      .toFixed(6);

    const tokenConversionRate =
      tokenConversionRates[destinationTokenInfo.address];
    const ethValueOfTrade = isSwapsDefaultTokenSymbol(
      destinationTokenInfo.symbol,
      chainId,
    )
      ? calcTokenAmount(destinationAmount, destinationTokenInfo.decimals).minus(
          rawEthFee,
          10,
        )
      : new BigNumber(tokenConversionRate || 0, 10)
          .times(
            calcTokenAmount(destinationAmount, destinationTokenInfo.decimals),
            10,
          )
          .minus(rawEthFee, 10);

    let liquiditySourceKey;
    let renderedSlippage = slippage;

    if (aggType === 'AGG') {
      liquiditySourceKey = 'swapAggregator';
    } else if (aggType === 'RFQ') {
      liquiditySourceKey = 'swapRequestForQuotation';
      renderedSlippage = 0;
    } else if (aggType === 'DEX') {
      liquiditySourceKey = 'swapDecentralizedExchange';
    } else if (aggType === 'CONTRACT') {
      liquiditySourceKey = 'swapDirectContract';
    } else {
      liquiditySourceKey = 'swapUnknown';
    }

    return {
      aggId: aggregator,
      amountReceiving: `${destinationValue} ${destinationTokenInfo.symbol}`,
      destinationTokenDecimals: destinationTokenInfo.decimals,
      destinationTokenSymbol: destinationTokenInfo.symbol,
      destinationTokenValue: formatSwapsValueForDisplay(destinationValue),
      destinationIconUrl: destinationTokenInfo.iconUrl,
      isBestQuote: quote.isBestQuote,
      liquiditySourceKey,
      feeInEth,
      detailedNetworkFees: `${feeInEth} (${feeInFiat})`,
      networkFees: feeInFiat,
      quoteSource: aggType,
      rawNetworkFees,
      slippage: renderedSlippage,
      sourceTokenDecimals: sourceTokenInfo.decimals,
      sourceTokenSymbol: sourceTokenInfo.symbol,
      sourceTokenValue: sourceValue,
      sourceTokenIconUrl: sourceTokenInfo.iconUrl,
      ethValueOfTrade,
      minimumAmountReceived,
      metaMaskFee: fee,
    };
  });
}

export function getSwapsTokensReceivedFromTxMeta(
  tokenSymbol,
  txMeta,
  tokenAddress,
  accountAddress,
  tokenDecimals,
  approvalTxMeta,
  chainId,
) {
  const txReceipt = txMeta?.txReceipt;
  const networkAndAccountSupports1559 =
    txMeta?.txReceipt?.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
  if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) {
    if (
      !txReceipt ||
      !txMeta ||
      !txMeta.postTxBalance ||
      !txMeta.preTxBalance
    ) {
      return null;
    }

    let approvalTxGasCost = '0x0';
    if (approvalTxMeta && approvalTxMeta.txReceipt) {
      approvalTxGasCost = calcGasTotal(
        approvalTxMeta.txReceipt.gasUsed,
        networkAndAccountSupports1559
          ? approvalTxMeta.txReceipt.effectiveGasPrice // Base fee + priority fee.
          : approvalTxMeta.txParams.gasPrice,
      );
    }

    const gasCost = calcGasTotal(
      txReceipt.gasUsed,
      networkAndAccountSupports1559
        ? txReceipt.effectiveGasPrice
        : txMeta.txParams.gasPrice,
    );
    const totalGasCost = new BigNumber(gasCost, 16)
      .plus(approvalTxGasCost, 16)
      .toString(16);

    const preTxBalanceLessGasCost = subtractCurrencies(
      txMeta.preTxBalance,
      totalGasCost,
      {
        aBase: 16,
        bBase: 16,
        toNumericBase: 'hex',
      },
    );

    const ethReceived = subtractCurrencies(
      txMeta.postTxBalance,
      preTxBalanceLessGasCost,
      {
        aBase: 16,
        bBase: 16,
        fromDenomination: 'WEI',
        toDenomination: 'ETH',
        toNumericBase: 'dec',
        numberOfDecimals: 6,
      },
    );
    return ethReceived;
  }
  const txReceiptLogs = txReceipt?.logs;
  if (txReceiptLogs && txReceipt?.status !== '0x0') {
    const tokenTransferLog = txReceiptLogs.find((txReceiptLog) => {
      const isTokenTransfer =
        txReceiptLog.topics &&
        txReceiptLog.topics[0] === TOKEN_TRANSFER_LOG_TOPIC_HASH;
      const isTransferFromGivenToken = txReceiptLog.address === tokenAddress;
      const isTransferFromGivenAddress =
        txReceiptLog.topics &&
        txReceiptLog.topics[2] &&
        txReceiptLog.topics[2].match(accountAddress.slice(2));
      return (
        isTokenTransfer &&
        isTransferFromGivenToken &&
        isTransferFromGivenAddress
      );
    });
    return tokenTransferLog
      ? toPrecisionWithoutTrailingZeros(
          calcTokenAmount(tokenTransferLog.data, tokenDecimals).toString(10),
          6,
        )
      : '';
  }
  return null;
}

export function formatSwapsValueForDisplay(destinationAmount) {
  let amountToDisplay = toPrecisionWithoutTrailingZeros(destinationAmount, 12);
  if (amountToDisplay.match(/e[+-]/u)) {
    amountToDisplay = new BigNumber(amountToDisplay).toFixed();
  }
  return amountToDisplay;
}

/**
 * Checks whether a contract address is valid before swapping tokens.
 *
 * @param {string} contractAddress - E.g. "0x881d40237659c251811cec9c364ef91dc08d300c" for mainnet
 * @param {string} chainId - The hex encoded chain ID to check
 * @returns {boolean} Whether a contract address is valid or not
 */
export const isContractAddressValid = (
  contractAddress,
  chainId = MAINNET_CHAIN_ID,
) => {
  if (!contractAddress || !ALLOWED_CONTRACT_ADDRESSES[chainId]) {
    return false;
  }
  return ALLOWED_CONTRACT_ADDRESSES[chainId].some(
    // Sometimes we get a contract address with a few upper-case chars and since addresses are
    // case-insensitive, we compare lowercase versions for validity.
    (allowedContractAddress) =>
      contractAddress.toLowerCase() === allowedContractAddress.toLowerCase(),
  );
};

/**
 * @param {string} chainId
 * @returns string e.g. ethereum, bsc or polygon
 */
export const getNetworkNameByChainId = (chainId) => {
  switch (chainId) {
    case MAINNET_CHAIN_ID:
      return ETHEREUM;
    case BSC_CHAIN_ID:
      return BSC;
    case POLYGON_CHAIN_ID:
      return POLYGON;
    case RINKEBY_CHAIN_ID:
      return RINKEBY;
    case AVALANCHE_CHAIN_ID:
      return AVALANCHE;
    default:
      return '';
  }
};

/**
 * It returns info about if Swaps are enabled and if we should use our new APIs for it.
 *
 * @param {object} swapsFeatureFlags
 * @param {string} chainId
 * @returns object with 2 items: "swapsFeatureIsLive"
 */
export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
  const networkName = getNetworkNameByChainId(chainId);
  // Use old APIs for testnet and Rinkeby.
  if ([LOCALHOST_CHAIN_ID, RINKEBY_CHAIN_ID].includes(chainId)) {
    return {
      swapsFeatureIsLive: true,
    };
  }
  // If a network name is not found in the list of feature flags, disable Swaps.
  if (!swapsFeatureFlags[networkName]) {
    return {
      swapsFeatureIsLive: false,
    };
  }
  const isNetworkEnabledForNewApi =
    swapsFeatureFlags[networkName].extension_active;
  if (isNetworkEnabledForNewApi) {
    return {
      swapsFeatureIsLive: true,
    };
  }
  return {
    swapsFeatureIsLive: swapsFeatureFlags[networkName].fallback_to_v1,
  };
};

/**
 * @param {number} value
 * @returns number
 */
export const countDecimals = (value) => {
  if (!value || Math.floor(value) === value) {
    return 0;
  }
  return value.toString().split('.')[1]?.length || 0;
};

export const showRemainingTimeInMinAndSec = (remainingTimeInSec) => {
  if (!Number.isInteger(remainingTimeInSec)) {
    return '0:00';
  }
  const minutes = Math.floor(remainingTimeInSec / 60);
  const seconds = remainingTimeInSec % 60;
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};

export const stxErrorTypes = ['unavailable', 'not_enough_funds'];

const smartTransactionsErrorMap = {
  unavailable: 'Smart Transactions are temporarily unavailable.',
  not_enough_funds: 'Not enough funds for a smart transaction.',
};

export const smartTransactionsErrorMessages = (errorType) => {
  return (
    smartTransactionsErrorMap[errorType] ||
    smartTransactionsErrorMap.unavailable
  );
};

export const parseSmartTransactionsError = (errorMessage) => {
  const errorJson = errorMessage.slice(12);
  return JSON.parse(errorJson.trim());
};