import React, { useState, useEffect, useRef, useContext } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
  Switch,
  Route,
  useLocation,
  useHistory,
  Redirect,
} from 'react-router-dom'
import BigNumber from 'bignumber.js'
import { I18nContext } from '../../contexts/i18n'
import {
  getSelectedAccount,
  getCurrentNetworkId,
} from '../../selectors/selectors'
import {
  getFromToken,
  getQuotes,
  clearSwapsState,
  getTradeTxId,
  getApproveTxId,
  getFetchingQuotes,
  setBalanceError,
  setTopAssets,
  getUsedSwapsGasPrice,
  getFetchParams,
  setAggregatorMetadata,
  getAggregatorMetadata,
  getBackgroundSwapRouteState,
  getSwapsErrorKey,
  getSwapsFeatureLiveness,
  prepareToLeaveSwaps,
  fetchAndSetSwapsGasPriceInfo,
} from '../../ducks/swaps/swaps'
import {
  AWAITING_SWAP_ROUTE,
  BUILD_QUOTE_ROUTE,
  VIEW_QUOTE_ROUTE,
  LOADING_QUOTES_ROUTE,
  SWAPS_ERROR_ROUTE,
  DEFAULT_ROUTE,
  SWAPS_MAINTENANCE_ROUTE,
} from '../../helpers/constants/routes'
import {
  ERROR_FETCHING_QUOTES,
  QUOTES_NOT_AVAILABLE_ERROR,
  ETH_SWAPS_TOKEN_OBJECT,
  SWAP_FAILED_ERROR,
  OFFLINE_FOR_MAINTENANCE,
} from '../../helpers/constants/swaps'

import {
  resetBackgroundSwapsState,
  setSwapsTokens,
  removeToken,
  setBackgroundSwapRouteState,
  setSwapsErrorKey,
} from '../../store/actions'
import {
  currentNetworkTxListSelector,
  getRpcPrefsForCurrentProvider,
} from '../../selectors'
import { useNewMetricEvent } from '../../hooks/useMetricEvent'
import { getValueFromWeiHex } from '../../helpers/utils/conversions.util'

import FeatureToggledRoute from '../../helpers/higher-order-components/feature-toggled-route'
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'
import {
  fetchTokens,
  fetchTopAssets,
  getSwapsTokensReceivedFromTxMeta,
  fetchAggregatorMetadata,
} from './swaps.util'
import AwaitingSwap from './awaiting-swap'
import LoadingQuote from './loading-swaps-quotes'
import BuildQuote from './build-quote'
import ViewQuote from './view-quote'

export default function Swap() {
  const t = useContext(I18nContext)
  const history = useHistory()
  const dispatch = useDispatch()

  const { pathname } = useLocation()
  const isAwaitingSwapRoute = pathname === AWAITING_SWAP_ROUTE
  const isSwapsErrorRoute = pathname === SWAPS_ERROR_ROUTE
  const isLoadingQuotesRoute = pathname === LOADING_QUOTES_ROUTE

  const fetchParams = useSelector(getFetchParams)
  const { sourceTokenInfo = {}, destinationTokenInfo = {} } =
    fetchParams?.metaData || {}

  const [inputValue, setInputValue] = useState(fetchParams?.value || '')
  const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 2)

  const routeState = useSelector(getBackgroundSwapRouteState)
  const usedGasPrice = useSelector(getUsedSwapsGasPrice)
  const selectedAccount = useSelector(getSelectedAccount)
  const quotes = useSelector(getQuotes)
  const txList = useSelector(currentNetworkTxListSelector)
  const tradeTxId = useSelector(getTradeTxId)
  const approveTxId = useSelector(getApproveTxId)
  const aggregatorMetadata = useSelector(getAggregatorMetadata)
  const networkId = useSelector(getCurrentNetworkId)
  const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider)
  const fetchingQuotes = useSelector(getFetchingQuotes)
  let swapsErrorKey = useSelector(getSwapsErrorKey)
  const swapsEnabled = useSelector(getSwapsFeatureLiveness)

  const {
    balance: ethBalance,
    address: selectedAccountAddress,
  } = selectedAccount
  const fetchParamsFromToken =
    sourceTokenInfo?.symbol === 'ETH'
      ? {
          ...ETH_SWAPS_TOKEN_OBJECT,
          string: getValueFromWeiHex({
            value: ethBalance,
            numberOfDecimals: 4,
            toDenomination: 'ETH',
          }),
          balance: ethBalance,
        }
      : sourceTokenInfo
  const selectedFromToken =
    useSelector(getFromToken) || fetchParamsFromToken || {}
  const { destinationTokenAddedForSwap } = fetchParams || {}

  const approveTxData =
    approveTxId && txList.find(({ id }) => approveTxId === id)
  const tradeTxData = tradeTxId && txList.find(({ id }) => tradeTxId === id)
  const tokensReceived =
    tradeTxData?.txReceipt &&
    getSwapsTokensReceivedFromTxMeta(
      destinationTokenInfo?.symbol,
      tradeTxData,
      destinationTokenInfo?.address,
      selectedAccountAddress,
      destinationTokenInfo?.decimals,
      approveTxData,
    )
  const tradeConfirmed = tradeTxData?.status === TRANSACTION_STATUSES.CONFIRMED
  const approveError =
    approveTxData?.status === TRANSACTION_STATUSES.FAILED ||
    approveTxData?.txReceipt?.status === '0x0'
  const tradeError =
    tradeTxData?.status === TRANSACTION_STATUSES.FAILED ||
    tradeTxData?.txReceipt?.status === '0x0'
  const conversionError = approveError || tradeError

  if (conversionError) {
    swapsErrorKey = SWAP_FAILED_ERROR
  }

  const clearTemporaryTokenRef = useRef()
  useEffect(() => {
    clearTemporaryTokenRef.current = () => {
      if (
        destinationTokenAddedForSwap &&
        (!isAwaitingSwapRoute || conversionError)
      ) {
        dispatch(removeToken(destinationTokenInfo?.address))
      }
    }
  }, [
    conversionError,
    dispatch,
    destinationTokenAddedForSwap,
    destinationTokenInfo,
    fetchParams,
    isAwaitingSwapRoute,
  ])
  useEffect(() => {
    return () => {
      clearTemporaryTokenRef.current()
    }
  }, [])

  useEffect(() => {
    fetchTokens()
      .then((tokens) => {
        dispatch(setSwapsTokens(tokens))
      })
      .catch((error) => console.error(error))

    fetchTopAssets().then((topAssets) => {
      dispatch(setTopAssets(topAssets))
    })

    fetchAggregatorMetadata().then((newAggregatorMetadata) => {
      dispatch(setAggregatorMetadata(newAggregatorMetadata))
    })

    dispatch(fetchAndSetSwapsGasPriceInfo())

    return () => {
      dispatch(prepareToLeaveSwaps())
    }
  }, [dispatch])

  const exitedSwapsEvent = useNewMetricEvent({
    event: 'Exited Swaps',
    category: 'swaps',
    sensitiveProperties: {
      token_from: fetchParams?.sourceTokenInfo?.symbol,
      token_from_amount: fetchParams?.value,
      request_type: fetchParams?.balanceError,
      token_to: fetchParams?.destinationTokenInfo?.symbol,
      slippage: fetchParams?.slippage,
      custom_slippage: fetchParams?.slippage !== 2,
      current_screen: pathname.match(/\/swaps\/(.+)/u)[1],
    },
  })
  const exitEventRef = useRef()
  useEffect(() => {
    exitEventRef.current = () => {
      exitedSwapsEvent()
    }
  })

  useEffect(() => {
    return () => {
      exitEventRef.current()
    }
  }, [])

  useEffect(() => {
    if (swapsErrorKey && !isSwapsErrorRoute) {
      history.push(SWAPS_ERROR_ROUTE)
    }
  }, [history, swapsErrorKey, isSwapsErrorRoute])

  const beforeUnloadEventAddedRef = useRef()
  useEffect(() => {
    const fn = () => {
      clearTemporaryTokenRef.current()
      if (isLoadingQuotesRoute) {
        dispatch(prepareToLeaveSwaps())
      }
      return null
    }
    if (isLoadingQuotesRoute && !beforeUnloadEventAddedRef.current) {
      beforeUnloadEventAddedRef.current = true
      window.addEventListener('beforeunload', fn)
    }
    return () => window.removeEventListener('beforeunload', fn)
  }, [dispatch, isLoadingQuotesRoute])

  return (
    <div className="swaps">
      <div className="swaps__container">
        <div className="swaps__header">
          <div className="swaps__title">{t('swap')}</div>
          {!isAwaitingSwapRoute && (
            <div
              className="swaps__header-cancel"
              onClick={async () => {
                clearTemporaryTokenRef.current()
                dispatch(clearSwapsState())
                await dispatch(resetBackgroundSwapsState())
                history.push(DEFAULT_ROUTE)
              }}
            >
              {t('cancel')}
            </div>
          )}
        </div>
        <div className="swaps__content">
          <Switch>
            <FeatureToggledRoute
              redirectRoute={SWAPS_MAINTENANCE_ROUTE}
              flag={swapsEnabled}
              path={BUILD_QUOTE_ROUTE}
              exact
              render={() => {
                if (tradeTxData && !conversionError) {
                  return <Redirect to={{ pathname: AWAITING_SWAP_ROUTE }} />
                } else if (tradeTxData) {
                  return <Redirect to={{ pathname: SWAPS_ERROR_ROUTE }} />
                } else if (routeState === 'loading' && aggregatorMetadata) {
                  return <Redirect to={{ pathname: LOADING_QUOTES_ROUTE }} />
                }

                const onInputChange = (newInputValue, balance) => {
                  setInputValue(newInputValue)
                  dispatch(
                    setBalanceError(
                      new BigNumber(newInputValue || 0).gt(balance || 0),
                    ),
                  )
                }

                return (
                  <BuildQuote
                    inputValue={inputValue}
                    selectedFromToken={selectedFromToken}
                    onInputChange={onInputChange}
                    ethBalance={ethBalance}
                    setMaxSlippage={setMaxSlippage}
                    selectedAccountAddress={selectedAccountAddress}
                    maxSlippage={Number(maxSlippage)}
                  />
                )
              }}
            />
            <FeatureToggledRoute
              redirectRoute={SWAPS_MAINTENANCE_ROUTE}
              flag={swapsEnabled}
              path={VIEW_QUOTE_ROUTE}
              exact
              render={() => {
                if (Object.values(quotes).length) {
                  return (
                    <ViewQuote numberOfQuotes={Object.values(quotes).length} />
                  )
                } else if (fetchParams) {
                  return <Redirect to={{ pathname: SWAPS_ERROR_ROUTE }} />
                }
                return <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />
              }}
            />
            <Route
              path={SWAPS_ERROR_ROUTE}
              exact
              render={() => {
                if (swapsErrorKey) {
                  return (
                    <AwaitingSwap
                      swapComplete={false}
                      errorKey={swapsErrorKey}
                      txHash={tradeTxData?.hash}
                      networkId={networkId}
                      rpcPrefs={rpcPrefs}
                      inputValue={inputValue}
                      maxSlippage={maxSlippage}
                      submittedTime={tradeTxData?.submittedTime}
                    />
                  )
                }
                return <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />
              }}
            />
            <FeatureToggledRoute
              redirectRoute={SWAPS_MAINTENANCE_ROUTE}
              flag={swapsEnabled}
              path={LOADING_QUOTES_ROUTE}
              exact
              render={() => {
                return aggregatorMetadata ? (
                  <LoadingQuote
                    loadingComplete={
                      !fetchingQuotes && Boolean(Object.values(quotes).length)
                    }
                    onDone={async () => {
                      await dispatch(setBackgroundSwapRouteState(''))

                      if (
                        swapsErrorKey === ERROR_FETCHING_QUOTES ||
                        swapsErrorKey === QUOTES_NOT_AVAILABLE_ERROR
                      ) {
                        dispatch(setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR))
                        history.push(SWAPS_ERROR_ROUTE)
                      } else {
                        history.push(VIEW_QUOTE_ROUTE)
                      }
                    }}
                    aggregatorMetadata={aggregatorMetadata}
                  />
                ) : (
                  <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />
                )
              }}
            />
            <Route
              path={SWAPS_MAINTENANCE_ROUTE}
              exact
              render={() => {
                return swapsEnabled === false ? (
                  <AwaitingSwap
                    errorKey={OFFLINE_FOR_MAINTENANCE}
                    networkId={networkId}
                    rpcPrefs={rpcPrefs}
                  />
                ) : (
                  <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />
                )
              }}
            />
            <Route
              path={AWAITING_SWAP_ROUTE}
              exact
              render={() => {
                return routeState === 'awaiting' || tradeTxData ? (
                  <AwaitingSwap
                    swapComplete={tradeConfirmed}
                    networkId={networkId}
                    txHash={tradeTxData?.hash}
                    tokensReceived={tokensReceived}
                    tradeTxData={tradeTxData}
                    usedGasPrice={usedGasPrice}
                    submittingSwap={
                      routeState === 'awaiting' && !(approveTxId || tradeTxId)
                    }
                    rpcPrefs={rpcPrefs}
                    inputValue={inputValue}
                    maxSlippage={maxSlippage}
                  />
                ) : (
                  <Redirect to={{ pathname: DEFAULT_ROUTE }} />
                )
              }}
            />
          </Switch>
        </div>
      </div>
    </div>
  )
}
