import EventEmitter from 'events'
import React, { useContext, useRef, useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { useHistory } from 'react-router-dom'
import { I18nContext } from '../../../contexts/i18n'
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'
import { MetaMetricsContext } from '../../../contexts/metametrics.new'
import { getCurrentCurrency, getUSDConversionRate } from '../../../selectors'
import {
  getUsedQuote,
  getFetchParams,
  getApproveTxParams,
  getUsedSwapsGasPrice,
  fetchQuotesAndSetQuoteState,
  navigateBackToBuildQuote,
  prepareForRetryGetQuotes,
  prepareToLeaveSwaps,
} from '../../../ducks/swaps/swaps'
import { useTransactionTimeRemaining } from '../../../hooks/useTransactionTimeRemaining'
import { usePrevious } from '../../../hooks/usePrevious'
import Mascot from '../../../components/ui/mascot'
import PulseLoader from '../../../components/ui/pulse-loader'
import {
  getBlockExplorerUrlForTx,
  getStatusKey,
} from '../../../helpers/utils/transactions.util'
import CountdownTimer from '../countdown-timer'
import {
  QUOTES_EXPIRED_ERROR,
  SWAP_FAILED_ERROR,
  ERROR_FETCHING_QUOTES,
  QUOTES_NOT_AVAILABLE_ERROR,
  OFFLINE_FOR_MAINTENANCE,
} from '../../../helpers/constants/swaps'
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes'

import { getRenderableNetworkFeesForQuote } from '../swaps.util'
import SwapsFooter from '../swaps-footer'
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'
import SwapFailureIcon from './swap-failure-icon'
import SwapSuccessIcon from './swap-success-icon'
import QuotesTimeoutIcon from './quotes-timeout-icon'
import ViewOnEtherScanLink from './view-on-ether-scan-link'

export default function AwaitingSwap({
  swapComplete,
  errorKey,
  txHash,
  networkId,
  tokensReceived,
  rpcPrefs,
  submittingSwap,
  tradeTxData,
  usedGasPrice,
  inputValue,
  maxSlippage,
}) {
  const t = useContext(I18nContext)
  const metaMetricsEvent = useContext(MetaMetricsContext)
  const history = useHistory()
  const dispatch = useDispatch()
  const animationEventEmitter = useRef(new EventEmitter())

  const fetchParams = useSelector(getFetchParams)
  const { destinationTokenInfo, sourceTokenInfo } = fetchParams?.metaData || {}
  const usedQuote = useSelector(getUsedQuote)
  const approveTxParams = useSelector(getApproveTxParams)
  const swapsGasPrice = useSelector(getUsedSwapsGasPrice)
  const currentCurrency = useSelector(getCurrentCurrency)
  const usdConversionRate = useSelector(getUSDConversionRate)

  const [timeRemainingExpired, setTimeRemainingExpired] = useState(false)
  const [trackedQuotesExpiredEvent, setTrackedQuotesExpiredEvent] = useState(
    false,
  )

  let feeinUnformattedFiat

  if (usedQuote && swapsGasPrice) {
    const renderableNetworkFees = getRenderableNetworkFeesForQuote(
      usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
      approveTxParams?.gas || '0x0',
      swapsGasPrice,
      currentCurrency,
      usdConversionRate,
      usedQuote?.trade?.value,
      sourceTokenInfo?.symbol,
      usedQuote.sourceAmount,
    )
    feeinUnformattedFiat = renderableNetworkFees.rawNetworkFees
  }

  const quotesExpiredEvent = useNewMetricEvent({
    event: 'Quotes Timed Out',
    sensitiveProperties: {
      token_from: sourceTokenInfo?.symbol,
      token_from_amount: fetchParams?.value,
      token_to: destinationTokenInfo?.symbol,
      request_type: fetchParams?.balanceError ? 'Quote' : 'Order',
      slippage: fetchParams?.slippage,
      custom_slippage: fetchParams?.slippage === 2,
      gas_fees: feeinUnformattedFiat,
    },
    category: 'swaps',
  })

  const blockExplorerUrl =
    txHash && getBlockExplorerUrlForTx(networkId, txHash, rpcPrefs)

  const statusKey = tradeTxData && getStatusKey(tradeTxData)
  const timeRemaining = useTransactionTimeRemaining(
    statusKey === TRANSACTION_STATUSES.SUBMITTED,
    true,
    tradeTxData?.submittedTime,
    usedGasPrice,
    true,
    true,
  )
  const previousTimeRemaining = usePrevious(timeRemaining)
  const timeRemainingIsNumber =
    typeof timeRemaining === 'number' && !isNaN(timeRemaining)
  const previousTimeRemainingIsNumber =
    typeof previousTimeRemaining === 'number' && !isNaN(previousTimeRemaining)
  const estimatedTransactionWaitTime = timeRemaining * 1000 * 60

  useEffect(() => {
    if (
      !timeRemainingIsNumber &&
      previousTimeRemainingIsNumber &&
      !timeRemainingExpired
    ) {
      setTimeRemainingExpired(true)
    }
  }, [
    timeRemainingIsNumber,
    previousTimeRemainingIsNumber,
    timeRemainingExpired,
  ])

  let countdownText
  if (
    timeRemainingIsNumber &&
    !timeRemainingExpired &&
    tradeTxData?.submittedTime
  ) {
    countdownText = (
      <CountdownTimer
        key="countdown-timer"
        timeStarted={tradeTxData?.submittedTime}
        timerBase={estimatedTransactionWaitTime}
        timeOnly
      />
    )
  } else if (tradeTxData?.submittedTime) {
    countdownText = t('swapsAlmostDone')
  } else {
    countdownText = t('swapEstimatedTimeCalculating')
  }

  let headerText
  let statusImage
  let descriptionText
  let submitText
  let content

  if (errorKey === OFFLINE_FOR_MAINTENANCE) {
    headerText = t('offlineForMaintenance')
    descriptionText = t('metamaskSwapsOfflineDescription')
    submitText = t('close')
    statusImage = <SwapFailureIcon />
  } else if (errorKey === SWAP_FAILED_ERROR) {
    headerText = t('swapFailedErrorTitle')
    descriptionText = t('swapFailedErrorDescription')
    submitText = t('tryAgain')
    statusImage = <SwapFailureIcon />
    content = blockExplorerUrl && (
      <ViewOnEtherScanLink
        txHash={txHash}
        blockExplorerUrl={blockExplorerUrl}
        isCustomBlockExplorerUrl={Boolean(rpcPrefs.blockExplorerUrl)}
      />
    )
  } else if (errorKey === QUOTES_EXPIRED_ERROR) {
    headerText = t('swapQuotesExpiredErrorTitle')
    descriptionText = t('swapQuotesExpiredErrorDescription')
    submitText = t('tryAgain')
    statusImage = <QuotesTimeoutIcon />

    if (!trackedQuotesExpiredEvent) {
      setTrackedQuotesExpiredEvent(true)
      quotesExpiredEvent()
    }
  } else if (errorKey === ERROR_FETCHING_QUOTES) {
    headerText = t('swapFetchingQuotesErrorTitle')
    descriptionText = t('swapFetchingQuotesErrorDescription')
    submitText = t('back')
    statusImage = <SwapFailureIcon />
  } else if (errorKey === QUOTES_NOT_AVAILABLE_ERROR) {
    headerText = t('swapQuotesNotAvailableErrorTitle')
    descriptionText = t('swapQuotesNotAvailableErrorDescription')
    submitText = t('tryAgain')
    statusImage = <SwapFailureIcon />
  } else if (!errorKey && !swapComplete) {
    /**
     * only show estimated time if the transaction has a submitted time, the swap has
     * not yet completed and there isn't an error. If the swap has not completed and
     * there is no error, but also has no submitted time (has not yet been published),
     * then we apply the invisible class to the time estimate div. This creates consistent
     * spacing before and after display of the time estimate.
     */
    headerText = t('swapProcessing')
    statusImage = <PulseLoader />
    submitText = t('swapsViewInActivity')
    descriptionText = t('swapOnceTransactionHasProcess', [
      <span
        key="swapOnceTransactionHasProcess-1"
        className="awaiting-swap__amount-and-symbol"
      >
        {destinationTokenInfo.symbol}
      </span>,
    ])
    content = (
      <>
        <div
          className={classnames('awaiting-swap__time-estimate', {
            'awaiting-swap__time-estimate--invisible': !tradeTxData?.submittedTime,
          })}
        >
          {t('swapEstimatedTimeFull', [
            <span
              className="awaiting-swap__time-estimate-text"
              key="swapEstimatedTime-1"
            >
              {t('swapEstimatedTime')}
            </span>,
            countdownText,
          ])}
        </div>
        {blockExplorerUrl && (
          <ViewOnEtherScanLink
            txHash={txHash}
            blockExplorerUrl={blockExplorerUrl}
            isCustomBlockExplorerUrl={Boolean(rpcPrefs.blockExplorerUrl)}
          />
        )}
      </>
    )
  } else if (!errorKey && swapComplete) {
    headerText = t('swapTransactionComplete')
    statusImage = <SwapSuccessIcon />
    submitText = t('swapViewToken', [destinationTokenInfo.symbol])
    descriptionText = t('swapTokenAvailable', [
      <span
        key="swapTokenAvailable-2"
        className="awaiting-swap__amount-and-symbol"
      >
        {`${tokensReceived || ''} ${destinationTokenInfo.symbol}`}
      </span>,
    ])
    content = blockExplorerUrl && (
      <ViewOnEtherScanLink
        txHash={txHash}
        blockExplorerUrl={blockExplorerUrl}
        isCustomBlockExplorerUrl={Boolean(rpcPrefs.blockExplorerUrl)}
      />
    )
  }

  return (
    <div className="awaiting-swap">
      <div className="awaiting-swap__content">
        {!(swapComplete || errorKey) && (
          <Mascot
            animationEventEmitter={animationEventEmitter.current}
            width="90"
            height="90"
          />
        )}
        <div className="awaiting-swap__status-image">{statusImage}</div>
        <div className="awaiting-swap__header">{headerText}</div>
        <div className="awaiting-swap__main-descrption">{descriptionText}</div>
        {content}
      </div>
      <SwapsFooter
        onSubmit={async () => {
          if (errorKey === OFFLINE_FOR_MAINTENANCE) {
            await dispatch(prepareToLeaveSwaps())
            history.push(DEFAULT_ROUTE)
          } else if (errorKey === QUOTES_EXPIRED_ERROR) {
            dispatch(prepareForRetryGetQuotes())
            await dispatch(
              fetchQuotesAndSetQuoteState(
                history,
                inputValue,
                maxSlippage,
                metaMetricsEvent,
              ),
            )
          } else if (errorKey) {
            await dispatch(navigateBackToBuildQuote(history))
          } else if (destinationTokenInfo?.symbol === 'ETH') {
            history.push(DEFAULT_ROUTE)
          } else {
            history.push(`${ASSET_ROUTE}/${destinationTokenInfo?.address}`)
          }
        }}
        onCancel={async () => await dispatch(navigateBackToBuildQuote(history))}
        submitText={submitText}
        disabled={submittingSwap}
        hideCancel={errorKey !== QUOTES_EXPIRED_ERROR}
      />
    </div>
  )
}

AwaitingSwap.propTypes = {
  swapComplete: PropTypes.bool,
  networkId: PropTypes.string.isRequired,
  txHash: PropTypes.string,
  tokensReceived: PropTypes.string,
  rpcPrefs: PropTypes.object.isRequired,
  errorKey: PropTypes.oneOf([
    QUOTES_EXPIRED_ERROR,
    SWAP_FAILED_ERROR,
    ERROR_FETCHING_QUOTES,
    QUOTES_NOT_AVAILABLE_ERROR,
    OFFLINE_FOR_MAINTENANCE,
  ]),
  submittingSwap: PropTypes.bool,
  tradeTxData: PropTypes.object,
  usedGasPrice: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  inputValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  maxSlippage: PropTypes.number,
}
