import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import contractMap from 'eth-contract-metadata'
import BigNumber from 'bignumber.js'
import { isEqual, shuffle } from 'lodash'
import { checksumAddress } from '../helpers/utils/util'
import { getTokenFiatAmount } from '../helpers/utils/token-util'
import {
  getTokenExchangeRates,
  getConversionRate,
  getCurrentCurrency,
} from '../selectors'
import { getSwapsTokens } from '../ducks/swaps/swaps'
import { useSwapsEthToken } from './useSwapsEthToken'
import { useEqualityCheck } from './useEqualityCheck'

const tokenList = shuffle(
  Object.entries(contractMap)
    .map(([address, tokenData]) => ({
      ...tokenData,
      address: address.toLowerCase(),
    }))
    .filter((tokenData) => Boolean(tokenData.erc20)),
)

export function getRenderableTokenData(
  token,
  contractExchangeRates,
  conversionRate,
  currentCurrency,
) {
  const { symbol, name, address, iconUrl, string, balance, decimals } = token

  const formattedFiat =
    getTokenFiatAmount(
      symbol === 'ETH' ? 1 : contractExchangeRates[address],
      conversionRate,
      currentCurrency,
      string,
      symbol,
      true,
    ) || ''
  const rawFiat =
    getTokenFiatAmount(
      symbol === 'ETH' ? 1 : contractExchangeRates[address],
      conversionRate,
      currentCurrency,
      string,
      symbol,
      false,
    ) || ''
  const usedIconUrl =
    iconUrl ||
    (contractMap[checksumAddress(address)] &&
      `images/contract/${contractMap[checksumAddress(address)].logo}`)
  return {
    ...token,
    primaryLabel: symbol,
    secondaryLabel: name || contractMap[checksumAddress(address)]?.name,
    rightPrimaryLabel:
      string && `${new BigNumber(string).round(6).toString()} ${symbol}`,
    rightSecondaryLabel: formattedFiat,
    iconUrl: usedIconUrl,
    identiconAddress: usedIconUrl ? null : address,
    balance,
    decimals,
    name: name || contractMap[checksumAddress(address)]?.name,
    rawFiat,
  }
}

export function useTokensToSearch({
  providedTokens,
  usersTokens = [],
  topTokens = {},
  onlyEth,
  singleToken,
}) {
  const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual)
  const conversionRate = useSelector(getConversionRate)
  const currentCurrency = useSelector(getCurrentCurrency)

  const memoizedTopTokens = useEqualityCheck(topTokens)
  const memoizedUsersToken = useEqualityCheck(usersTokens)

  const swapsEthToken = useSwapsEthToken()
  const ethToken = getRenderableTokenData(
    swapsEthToken,
    tokenConversionRates,
    conversionRate,
    currentCurrency,
  )
  const memoizedEthToken = useEqualityCheck(ethToken)

  const swapsTokens = useSelector(getSwapsTokens) || []
  let tokensToSearch
  if (onlyEth) {
    tokensToSearch = [memoizedEthToken]
  } else if (singleToken) {
    tokensToSearch = providedTokens
  } else if (providedTokens) {
    tokensToSearch = [memoizedEthToken, ...providedTokens]
  } else if (swapsTokens.length) {
    tokensToSearch = [memoizedEthToken, ...swapsTokens]
  } else {
    tokensToSearch = [memoizedEthToken, ...tokenList]
  }
  const memoizedTokensToSearch = useEqualityCheck(tokensToSearch)
  return useMemo(() => {
    const usersTokensAddressMap = memoizedUsersToken.reduce(
      (acc, token) => ({ ...acc, [token.address]: token }),
      {},
    )

    const tokensToSearchBuckets = {
      owned: singleToken ? [] : [memoizedEthToken],
      top: [],
      others: [],
    }

    memoizedTokensToSearch.forEach((token) => {
      const renderableDataToken = getRenderableTokenData(
        { ...usersTokensAddressMap[token.address], ...token },
        tokenConversionRates,
        conversionRate,
        currentCurrency,
      )
      if (
        usersTokensAddressMap[token.address] &&
        (renderableDataToken.symbol === 'ETH' ||
          Number(renderableDataToken.balance ?? 0) !== 0)
      ) {
        tokensToSearchBuckets.owned.push(renderableDataToken)
      } else if (memoizedTopTokens[token.address]) {
        tokensToSearchBuckets.top[
          memoizedTopTokens[token.address].index
        ] = renderableDataToken
      } else {
        tokensToSearchBuckets.others.push(renderableDataToken)
      }
    })

    tokensToSearchBuckets.owned = tokensToSearchBuckets.owned.sort(
      ({ rawFiat }, { rawFiat: secondRawFiat }) => {
        return new BigNumber(rawFiat || 0).gt(secondRawFiat || 0) ? -1 : 1
      },
    )
    tokensToSearchBuckets.top = tokensToSearchBuckets.top.filter(
      (token) => token,
    )
    return [
      ...tokensToSearchBuckets.owned,
      ...tokensToSearchBuckets.top,
      ...tokensToSearchBuckets.others,
    ]
  }, [
    memoizedTokensToSearch,
    memoizedUsersToken,
    tokenConversionRates,
    conversionRate,
    currentCurrency,
    memoizedTopTokens,
    memoizedEthToken,
    singleToken,
  ])
}
