import React, { useState, useContext, useMemo, useEffect, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useHistory } from 'react-router-dom'
import BigNumber from 'bignumber.js'
import { isEqual } from 'lodash'
import classnames from 'classnames'
import { I18nContext } from '../../../contexts/i18n'
import SelectQuotePopover from '../select-quote-popover'
import { useEqualityCheck } from '../../../hooks/useEqualityCheck'
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'
import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken'
import { MetaMetricsContext } from '../../../contexts/metametrics.new'
import FeeCard from '../fee-card'
import { setCustomGasLimit } from '../../../ducks/gas/gas.duck'
import {
  getQuotes,
  getSelectedQuote,
  getApproveTxParams,
  getFetchParams,
  setBalanceError,
  getQuotesLastFetched,
  getBalanceError,
  getCustomSwapsGas,
  getDestinationTokenInfo,
  getSwapsTradeTxParams,
  getTopQuote,
  navigateBackToBuildQuote,
  signAndSendTransactions,
  getBackgroundSwapRouteState,
} from '../../../ducks/swaps/swaps'
import {
  conversionRateSelector,
  getSelectedAccount,
  getCurrentCurrency,
  getTokenExchangeRates,
} from '../../../selectors'
import { toPrecisionWithoutTrailingZeros } from '../../../helpers/utils/util'
import { getTokens } from '../../../ducks/metamask/metamask'
import {
  safeRefetchQuotes,
  setCustomApproveTxData,
  setSwapsTxGasLimit,
  setSelectedQuoteAggId,
  setSwapsErrorKey,
  showModal,
} from '../../../store/actions'
import {
  ASSET_ROUTE,
  BUILD_QUOTE_ROUTE,
  DEFAULT_ROUTE,
  SWAPS_ERROR_ROUTE,
  AWAITING_SWAP_ROUTE,
} from '../../../helpers/constants/routes'
import { getTokenData } from '../../../helpers/utils/transactions.util'
import {
  calcTokenAmount,
  calcTokenValue,
  getTokenValueParam,
} from '../../../helpers/utils/token-util'
import {
  decimalToHex,
  hexMax,
  hexToDecimal,
  getValueFromWeiHex,
} from '../../../helpers/utils/conversions.util'
import MainQuoteSummary from '../main-quote-summary'
import { calcGasTotal } from '../../send/send.utils'
import { getCustomTxParamsData } from '../../confirm-approve/confirm-approve.util'
import ActionableMessage from '../actionable-message'
import { quotesToRenderableData, getRenderableGasFeesForQuote } from '../swaps.util'
import { useTokenTracker } from '../../../hooks/useTokenTracker'
import { QUOTES_EXPIRED_ERROR } from '../../../helpers/constants/swaps'
import CountdownTimer from '../countdown-timer'
import SwapsFooter from '../swaps-footer'

export default function ViewQuote () {
  const history = useHistory()
  const dispatch = useDispatch()
  const t = useContext(I18nContext)
  const metaMetricsEvent = useContext(MetaMetricsContext)

  const [dispatchedSafeRefetch, setDispatchedSafeRefetch] = useState(false)
  const [selectQuotePopoverShown, setSelectQuotePopoverShown] = useState(false)
  const [warningHidden, setWarningHidden] = useState(false)
  const [originalApproveAmount, setOriginalApproveAmount] = useState(null)

  const routeState = useSelector(getBackgroundSwapRouteState)
  const quotes = useSelector(getQuotes, isEqual)
  useEffect(() => {
    if (!Object.values(quotes).length) {
      history.push(BUILD_QUOTE_ROUTE)
    } else if (routeState === 'awaiting') {
      history.push(AWAITING_SWAP_ROUTE)
    }
  }, [history, quotes, routeState])

  const quotesLastFetched = useSelector(getQuotesLastFetched)

  // Select necessary data
  const tradeTxParams = useSelector(getSwapsTradeTxParams)
  const { gasPrice } = tradeTxParams || {}
  const customMaxGas = useSelector(getCustomSwapsGas)
  const tokenConversionRates = useSelector(getTokenExchangeRates)
  const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates)
  const { balance: ethBalance } = useSelector(getSelectedAccount)
  const conversionRate = useSelector(conversionRateSelector)
  const currentCurrency = useSelector(getCurrentCurrency)
  const swapsTokens = useSelector(getTokens)
  const balanceError = useSelector(getBalanceError)
  const fetchParams = useSelector(getFetchParams)
  const approveTxParams = useSelector(getApproveTxParams)
  const selectedQuote = useSelector(getSelectedQuote)
  const topQuote = useSelector(getTopQuote)
  const usedQuote = selectedQuote || topQuote

  const { isBestQuote } = usedQuote
  const fetchParamsSourceToken = fetchParams?.sourceToken

  const usedGasLimit = (
    usedQuote?.gasEstimateWithRefund ||
    (`0x${decimalToHex(usedQuote?.averageGas || 0)}`)
  )

  const gasLimitForMax = (
    usedQuote?.gasEstimate ||
    (`0x${decimalToHex(usedQuote?.averageGas || 0)}`)
  )

  const usedGasLimitWithMultiplier = (new BigNumber(gasLimitForMax, 16)
    .times(1.4, 10))
    .round(0)
    .toString(16)

  const nonCustomMaxGasLimit = hexMax(
    (`0x${decimalToHex(usedQuote?.maxGas || 0)}`),
    usedGasLimitWithMultiplier,
  )
  const maxGasLimit = customMaxGas || nonCustomMaxGasLimit

  const gasTotalInWeiHex = calcGasTotal(maxGasLimit, gasPrice)

  const { tokensWithBalances } = useTokenTracker(swapsTokens)
  const swapsEthToken = useSwapsEthToken()
  const balanceToken = fetchParamsSourceToken === swapsEthToken.address
    ? swapsEthToken
    : tokensWithBalances.find(({ address }) => address === fetchParamsSourceToken)

  const selectedFromToken = balanceToken || usedQuote.sourceTokenInfo
  const tokenBalance = (
    tokensWithBalances?.length &&
    calcTokenAmount(
      selectedFromToken.balance || '0x0',
      selectedFromToken.decimals,
    ).toFixed(9)
  )

  const approveData = getTokenData(approveTxParams?.data)
  const approveValue = approveData && getTokenValueParam(approveData)
  const approveAmount = (
    approveValue && (selectedFromToken?.decimals !== undefined) &&
    calcTokenAmount(approveValue, selectedFromToken.decimals).toFixed(9)
  )
  const approveGas = approveTxParams?.gas
  const approveGasTotal = calcGasTotal(approveGas || '0x0', gasPrice)
  const approveGasTotalInEth = getValueFromWeiHex({
    value: approveGasTotal,
    toDenomination: 'ETH',
    numberOfDecimals: 4,
  })

  const renderablePopoverData = useMemo(() => {
    return quotesToRenderableData(
      quotes,
      gasPrice,
      conversionRate,
      currentCurrency,
      approveGas,
      memoizedTokenConversionRates,
    )
  }, [
    quotes,
    gasPrice,
    conversionRate,
    currentCurrency,
    approveGas,
    memoizedTokenConversionRates,
  ])

  const renderableDataForUsedQuote = renderablePopoverData.find(
    (renderablePopoverDatum) => (
      renderablePopoverDatum.aggId === usedQuote.aggregator
    ),
  )

  const {
    destinationTokenDecimals,
    destinationTokenSymbol,
    destinationTokenValue,
    sourceTokenDecimals,
    sourceTokenSymbol,
    sourceTokenValue,
  } = renderableDataForUsedQuote

  const { feeInFiat, feeInEth } = getRenderableGasFeesForQuote(
    usedGasLimit,
    approveGas,
    gasPrice,
    currentCurrency,
    conversionRate,
  )

  const {
    feeInFiat: maxFeeInFiat,
    feeInEth: maxFeeInEth,
  } = getRenderableGasFeesForQuote(
    maxGasLimit,
    approveGas,
    gasPrice,
    currentCurrency,
    conversionRate,
  )

  const tokenCost = (new BigNumber(usedQuote.sourceAmount))
  const ethCost = (new BigNumber(usedQuote.trade.value || 0, 10))
    .plus((new BigNumber(gasTotalInWeiHex, 16)))

  const insufficientTokens = (
    (tokensWithBalances?.length || balanceError) &&
    (tokenCost).gt(new BigNumber(selectedFromToken.balance || '0x0'))
  )

  const insufficientEth = (ethCost).gt(new BigNumber(ethBalance || '0x0'))

  const tokenBalanceNeeded = insufficientTokens
    ? toPrecisionWithoutTrailingZeros(
      calcTokenAmount(
        tokenCost,
        selectedFromToken.decimals,
      ).minus(tokenBalance).toString(10), 6,
    )
    : null

  const ethBalanceNeeded = insufficientEth
    ? toPrecisionWithoutTrailingZeros(
      ethCost.minus(ethBalance, 16).div('1000000000000000000', 10).toString(10),
      6,
    )
    : null

  const destinationToken = useSelector(getDestinationTokenInfo)

  useEffect(() => {
    if (insufficientTokens || insufficientEth) {
      dispatch(setBalanceError(true))
    } else if (balanceError && !insufficientTokens && !insufficientEth) {
      dispatch(setBalanceError(false))
    }
  }, [insufficientTokens, insufficientEth, balanceError, dispatch])

  useEffect(() => {
    const currentTime = Date.now()
    const timeSinceLastFetched = currentTime - quotesLastFetched
    if (timeSinceLastFetched > 60000 && !dispatchedSafeRefetch) {
      setDispatchedSafeRefetch(true)
      dispatch(safeRefetchQuotes())
    } else if (timeSinceLastFetched > 60000) {
      dispatch(setSwapsErrorKey(QUOTES_EXPIRED_ERROR))
      history.push(SWAPS_ERROR_ROUTE)
    }
  }, [quotesLastFetched, dispatchedSafeRefetch, dispatch, history])

  useEffect(() => {
    if (!originalApproveAmount && approveAmount) {
      setOriginalApproveAmount(approveAmount)
    }
  }, [originalApproveAmount, approveAmount])

  const showWarning = (
    (balanceError || tokenBalanceNeeded || ethBalanceNeeded) &&
    !warningHidden
  )

  const numberOfQuotes = Object.values(quotes).length
  const bestQuoteReviewedEventSent = useRef()
  const eventObjectBase = {
    token_from: sourceTokenSymbol,
    token_from_amount: sourceTokenValue,
    token_to: destinationTokenSymbol,
    token_to_amount: destinationTokenValue,
    request_type: fetchParams?.balanceError,
    slippage: fetchParams?.slippage,
    custom_slippage: fetchParams?.slippage !== 2,
    response_time: fetchParams?.responseTime,
    best_quote_source: topQuote?.aggregator,
    available_quotes: numberOfQuotes,
  }

  const anonymousAllAvailableQuotesOpened = useNewMetricEvent({
    event: 'All Available Quotes Opened',
    properties: {
      ...eventObjectBase,
      other_quote_selected: usedQuote?.aggregator !== topQuote?.aggregator,
      other_quote_selected_source: usedQuote?.aggregator === topQuote?.aggregator ? null : usedQuote?.aggregator,
    },
    excludeMetaMetricsId: true,
    category: 'swaps',
  })
  const allAvailableQuotesOpened = useNewMetricEvent({ event: 'All Available Quotes Opened', category: 'swaps' })
  const anonymousQuoteDetailsOpened = useNewMetricEvent({
    event: 'Quote Details Opened',
    properties: {
      ...eventObjectBase,
      other_quote_selected: usedQuote?.aggregator !== topQuote?.aggregator,
      other_quote_selected_source: usedQuote?.aggregator === topQuote?.aggregator ? null : usedQuote?.aggregator,
    },
    excludeMetaMetricsId: true,
    category: 'swaps',
  })
  const quoteDetailsOpened = useNewMetricEvent({ event: 'Quote Details Opened', category: 'swaps' })
  const anonymousEditSpendLimitOpened = useNewMetricEvent({
    event: 'Edit Spend Limit Opened',
    properties: {
      ...eventObjectBase,
      custom_spend_limit_set: originalApproveAmount === approveAmount,
      custom_spend_limit_amount: originalApproveAmount === approveAmount ? null : approveAmount,
    },
    excludeMetaMetricsId: true,
    category: 'swaps',
  })
  const editSpendLimitOpened = useNewMetricEvent({ event: 'Edit Spend Limit Opened', category: 'swaps' })

  const anonymousBestQuoteReviewedEvent = useNewMetricEvent({ event: 'Best Quote Reviewed', properties: { ...eventObjectBase, network_fees: feeInFiat }, excludeMetaMetricsId: true, category: 'swaps' })
  const bestQuoteReviewedEvent = useNewMetricEvent({ event: 'Best Quote Reviewed', category: 'swaps' })
  useEffect(() => {
    if (!bestQuoteReviewedEventSent.current && [sourceTokenSymbol, sourceTokenValue, destinationTokenSymbol, destinationTokenValue, fetchParams, topQuote, numberOfQuotes, feeInFiat].every((dep) => dep !== null && dep !== undefined)) {
      bestQuoteReviewedEventSent.current = true
      bestQuoteReviewedEvent()
      anonymousBestQuoteReviewedEvent()
    }
  }, [sourceTokenSymbol, sourceTokenValue, destinationTokenSymbol, destinationTokenValue, fetchParams, topQuote, numberOfQuotes, feeInFiat, bestQuoteReviewedEvent, anonymousBestQuoteReviewedEvent])

  const onFeeCardTokenApprovalClick = () => {
    anonymousEditSpendLimitOpened()
    editSpendLimitOpened()
    dispatch(showModal({
      name: 'EDIT_APPROVAL_PERMISSION',
      decimals: selectedFromToken.decimals,
      origin: 'MetaMask',
      setCustomAmount: (newCustomPermissionAmount) => {
        const customPermissionAmount = newCustomPermissionAmount === ''
          ? originalApproveAmount
          : newCustomPermissionAmount
        const newData = getCustomTxParamsData(
          approveTxParams.data,
          { customPermissionAmount, decimals: selectedFromToken.decimals },
        )

        if (customPermissionAmount?.length && approveTxParams.data !== newData) {
          dispatch(setCustomApproveTxData(newData))
        }
      },
      tokenAmount: originalApproveAmount,
      customTokenAmount: (
        originalApproveAmount === approveAmount
          ? null
          : approveAmount
      ),
      tokenBalance,
      tokenSymbol: selectedFromToken.symbol,
      requiredMinimum: calcTokenAmount(
        usedQuote.sourceAmount,
        selectedFromToken.decimals,
      ),
    }))
  }

  const onFeeCardMaxRowClick = () => dispatch(showModal({
    name: 'CUSTOMIZE_GAS',
    txData: { txParams: { ...tradeTxParams, gas: maxGasLimit } },
    isSwap: true,
    customGasLimitMessage: (
      approveGas
        ? t('extraApprovalGas', [hexToDecimal(approveGas)])
        : ''
    ),
    customTotalSupplement: approveGasTotal,
    extraInfoRow: (
      approveGas
        ? {
          label: t('approvalTxGasCost'),
          value: t('amountInEth', [approveGasTotalInEth]),
        }
        : null
    ),
    useFastestButtons: true,
    minimumGasLimit: Number(hexToDecimal(nonCustomMaxGasLimit)),
  }))

  const tokenApprovalTextComponent = (
    <span
      key="swaps-view-quote-approve-symbol-1"
      className="view-quote__bold"
    >
      {sourceTokenSymbol}
    </span>
  )

  const actionableMessage = t('swapApproveNeedMoreTokens', [
    <span
      key="swapApproveNeedMoreTokens-1"
      className="view-quote__bold"
    >
      {tokenBalanceNeeded || ethBalanceNeeded}
    </span>,
    tokenBalanceNeeded && !(sourceTokenSymbol === 'ETH')
      ? sourceTokenSymbol
      : 'ETH',
  ])

  return (
    <div className="view-quote">
      <div className="view-quote__content">
        {selectQuotePopoverShown && (
          <SelectQuotePopover
            quoteDataRows={renderablePopoverData}
            onClose={() => setSelectQuotePopoverShown(false)}
            onSubmit={(aggId) => {
              dispatch(setSelectedQuoteAggId(aggId))
              dispatch(setCustomGasLimit(null))
              dispatch(setSwapsTxGasLimit(''))
            }}
            swapToSymbol={destinationTokenSymbol}
            initialAggId={usedQuote.aggregator}
            onQuoteDetailsIsOpened={() => {
              anonymousQuoteDetailsOpened()
              quoteDetailsOpened()
            }}
          />
        )}
        <div className="view-quote__insufficient-eth-warning-wrapper">
          {showWarning && (
            <ActionableMessage
              message={actionableMessage}
              onClose={() => setWarningHidden(true)}
            />
          )}
        </div>
        <div
          className={classnames('view-quote__countdown-timer-container', {
            'view-quote__countdown-timer-container--thin': showWarning,
          })}
        >
          <CountdownTimer
            timeStarted={quotesLastFetched}
            warningTime="0:30"
            infoTooltipLabelKey="swapQuotesAreRefreshed"
            labelKey="swapNewQuoteIn"
          />
        </div>
        <div
          className={classnames('view-quote__main-quote-summary-container', {
            'view-quote__main-quote-summary-container--thin': showWarning,
          })}
        >
          <MainQuoteSummary
            sourceValue={calcTokenValue(sourceTokenValue, sourceTokenDecimals)}
            sourceDecimals={sourceTokenDecimals}
            sourceSymbol={sourceTokenSymbol}
            destinationValue={calcTokenValue(
              destinationTokenValue,
              destinationTokenDecimals,
            )}
            destinationDecimals={destinationTokenDecimals}
            destinationSymbol={destinationTokenSymbol}
            isBestQuote={isBestQuote}
          />
        </div>
        <div
          className="view-quote__view-other-button-container"
        >
          <div className="view-quote__view-other-button">
            {t('swapNQuotesAvailable', [Object.values(quotes).length])}
            <i className="fa fa-arrow-right" />
          </div>
          <div
            className="view-quote__view-other-button-fade"
            onClick={() => {
              anonymousAllAvailableQuotesOpened()
              allAvailableQuotesOpened()
              setSelectQuotePopoverShown(true)
            }}
          >
            {t('swapNQuotesAvailable', [Object.values(quotes).length])}
            <i className="fa fa-arrow-right" />
          </div>
        </div>
        <div
          className={classnames('view-quote__fee-card-container', {
            'view-quote__fee-card-container--thin': showWarning,
            'view-quote__fee-card-container--three-rows': approveTxParams && (!balanceError || warningHidden),
          })}
        >
          <FeeCard
            primaryFee={({
              fee: feeInEth,
              maxFee: maxFeeInEth,
            })}
            secondaryFee={({
              fee: feeInFiat,
              maxFee: maxFeeInFiat,
            })}
            onFeeCardMaxRowClick={onFeeCardMaxRowClick}
            hideTokenApprovalRow={
              !approveTxParams || (balanceError && !warningHidden)
            }
            tokenApprovalTextComponent={tokenApprovalTextComponent}
            tokenApprovalSourceTokenSymbol={sourceTokenSymbol}
            onTokenApprovalClick={onFeeCardTokenApprovalClick}
          />
        </div>
      </div>
      <SwapsFooter
        onSubmit={() => {
          if (!balanceError) {
            dispatch(signAndSendTransactions(history, metaMetricsEvent))
          } else if (destinationToken.symbol === 'ETH') {
            history.push(DEFAULT_ROUTE)
          } else {
            history.push(`${ASSET_ROUTE}/${destinationToken.address}`)
          }
        }}
        submitText={t('swap')}
        onCancel={async () => await dispatch(navigateBackToBuildQuote(history))}
        disabled={balanceError}
        showTermsOfService
        showTopBorder
      />
    </div>
  )
}