import { createSlice } from '@reduxjs/toolkit'
import BigNumber from 'bignumber.js'
import log from 'loglevel'

import {
  loadLocalStorageData,
  saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import {
  addToken,
  addUnapprovedTransaction,
  fetchAndSetQuotes,
  forceUpdateMetamaskState,
  resetSwapsPostFetchState,
  setBackgroundSwapRouteState,
  setInitialGasEstimate,
  setSwapsErrorKey,
  setSwapsTxGasPrice,
  setApproveTxId,
  setTradeTxId,
  stopPollingForQuotes,
  updateAndApproveTx,
  updateTransaction,
  resetBackgroundSwapsState,
  setSwapsLiveness,
  setSelectedQuoteAggId,
  setSwapsTxGasLimit,
} from '../../store/actions'
import {
  AWAITING_SWAP_ROUTE,
  BUILD_QUOTE_ROUTE,
  LOADING_QUOTES_ROUTE,
  SWAPS_ERROR_ROUTE,
  SWAPS_MAINTENANCE_ROUTE,
} from '../../helpers/constants/routes'
import {
  fetchSwapsFeatureLiveness,
  fetchSwapsGasPrices,
} from '../../pages/swaps/swaps.util'
import { calcGasTotal } from '../../pages/send/send.utils'
import {
  decimalToHex,
  getValueFromWeiHex,
  hexMax,
  decGWEIToHexWEI,
  hexToDecimal,
  hexWEIToDecGWEI,
} from '../../helpers/utils/conversions.util'
import { conversionLessThan } from '../../helpers/utils/conversion-util'
import { calcTokenAmount } from '../../helpers/utils/token-util'
import {
  getSelectedAccount,
  getTokenExchangeRates,
  getUSDConversionRate,
} from '../../selectors'
import {
  ERROR_FETCHING_QUOTES,
  QUOTES_NOT_AVAILABLE_ERROR,
  ETH_SWAPS_TOKEN_OBJECT,
  SWAP_FAILED_ERROR,
  SWAPS_FETCH_ORDER_CONFLICT,
} from '../../helpers/constants/swaps'
import { TRANSACTION_CATEGORIES } from '../../../../shared/constants/transaction'

const GAS_PRICES_LOADING_STATES = {
  INITIAL: 'INITIAL',
  LOADING: 'LOADING',
  FAILED: 'FAILED',
  COMPLETED: 'COMPLETED',
}

const initialState = {
  aggregatorMetadata: null,
  approveTxId: null,
  balanceError: false,
  fetchingQuotes: false,
  fromToken: null,
  quotesFetchStartTime: null,
  topAssets: {},
  toToken: null,
  customGas: {
    price: null,
    limit: null,
    loading: GAS_PRICES_LOADING_STATES.INITIAL,
    priceEstimates: {},
    priceEstimatesLastRetrieved: 0,
    fallBackPrice: null,
  },
}

const slice = createSlice({
  name: 'swaps',
  initialState,
  reducers: {
    clearSwapsState: () => initialState,
    navigatedBackToBuildQuote: (state) => {
      state.approveTxId = null
      state.balanceError = false
      state.fetchingQuotes = false
      state.customGas.limit = null
      state.customGas.price = null
    },
    retriedGetQuotes: (state) => {
      state.approveTxId = null
      state.balanceError = false
      state.fetchingQuotes = false
    },
    setAggregatorMetadata: (state, action) => {
      state.aggregatorMetadata = action.payload
    },
    setBalanceError: (state, action) => {
      state.balanceError = action.payload
    },
    setFetchingQuotes: (state, action) => {
      state.fetchingQuotes = action.payload
    },
    setFromToken: (state, action) => {
      state.fromToken = action.payload
    },
    setQuotesFetchStartTime: (state, action) => {
      state.quotesFetchStartTime = action.payload
    },
    setTopAssets: (state, action) => {
      state.topAssets = action.payload
    },
    setToToken: (state, action) => {
      state.toToken = action.payload
    },
    swapCustomGasModalClosed: (state) => {
      state.customGas.price = null
      state.customGas.limit = null
    },
    swapCustomGasModalPriceEdited: (state, action) => {
      state.customGas.price = action.payload
    },
    swapCustomGasModalLimitEdited: (state, action) => {
      state.customGas.limit = action.payload
    },
    swapGasPriceEstimatesFetchStarted: (state) => {
      state.customGas.loading = GAS_PRICES_LOADING_STATES.LOADING
    },
    swapGasPriceEstimatesFetchFailed: (state) => {
      state.customGas.loading = GAS_PRICES_LOADING_STATES.FAILED
    },
    swapGasPriceEstimatesFetchCompleted: (state, action) => {
      state.customGas.priceEstimates = action.payload.priceEstimates
      state.customGas.loading = GAS_PRICES_LOADING_STATES.COMPLETED
      state.customGas.priceEstimatesLastRetrieved =
        action.payload.priceEstimatesLastRetrieved
    },
    retrievedFallbackSwapsGasPrice: (state, action) => {
      state.customGas.fallBackPrice = action.payload
    },
  },
})

const { actions, reducer } = slice

export default reducer

// Selectors

export const getAggregatorMetadata = (state) => state.swaps.aggregatorMetadata

export const getBalanceError = (state) => state.swaps.balanceError

export const getFromToken = (state) => state.swaps.fromToken

export const getTopAssets = (state) => state.swaps.topAssets

export const getToToken = (state) => state.swaps.toToken

export const getFetchingQuotes = (state) => state.swaps.fetchingQuotes

export const getQuotesFetchStartTime = (state) =>
  state.swaps.quotesFetchStartTime

export const getSwapsCustomizationModalPrice = (state) =>
  state.swaps.customGas.price

export const getSwapsCustomizationModalLimit = (state) =>
  state.swaps.customGas.limit

export const swapGasPriceEstimateIsLoading = (state) =>
  state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.LOADING

export const swapGasEstimateLoadingHasFailed = (state) =>
  state.swaps.customGas.loading === GAS_PRICES_LOADING_STATES.INITIAL

export const getSwapGasPriceEstimateData = (state) =>
  state.swaps.customGas.priceEstimates

export const getSwapsPriceEstimatesLastRetrieved = (state) =>
  state.swaps.customGas.priceEstimatesLastRetrieved

export const getSwapsFallbackGasPrice = (state) =>
  state.swaps.customGas.fallBackPrice

export function shouldShowCustomPriceTooLowWarning(state) {
  const { average } = getSwapGasPriceEstimateData(state)

  const customGasPrice = getSwapsCustomizationModalPrice(state)

  if (!customGasPrice || average === undefined) {
    return false
  }

  const customPriceRisksSwapFailure = conversionLessThan(
    {
      value: customGasPrice,
      fromNumericBase: 'hex',
      fromDenomination: 'WEI',
      toDenomination: 'GWEI',
    },
    { value: average, fromNumericBase: 'dec' },
  )

  return customPriceRisksSwapFailure
}

// Background selectors

const getSwapsState = (state) => state.metamask.swapsState

export const getSwapsFeatureLiveness = (state) =>
  state.metamask.swapsState.swapsFeatureIsLive

export const getBackgroundSwapRouteState = (state) =>
  state.metamask.swapsState.routeState

export const getCustomSwapsGas = (state) =>
  state.metamask.swapsState.customMaxGas

export const getCustomSwapsGasPrice = (state) =>
  state.metamask.swapsState.customGasPrice

export const getFetchParams = (state) => state.metamask.swapsState.fetchParams

export const getQuotes = (state) => state.metamask.swapsState.quotes

export const getQuotesLastFetched = (state) =>
  state.metamask.swapsState.quotesLastFetched

export const getSelectedQuote = (state) => {
  const { selectedAggId, quotes } = getSwapsState(state)
  return quotes[selectedAggId]
}

export const getSwapsErrorKey = (state) => getSwapsState(state)?.errorKey

export const getShowQuoteLoadingScreen = (state) =>
  state.swaps.showQuoteLoadingScreen

export const getSwapsTokens = (state) => state.metamask.swapsState.tokens

export const getSwapsWelcomeMessageSeenStatus = (state) =>
  state.metamask.swapsWelcomeMessageHasBeenShown

export const getTopQuote = (state) => {
  const { topAggId, quotes } = getSwapsState(state)
  return quotes[topAggId]
}

export const getApproveTxId = (state) => state.metamask.swapsState.approveTxId

export const getTradeTxId = (state) => state.metamask.swapsState.tradeTxId

export const getUsedQuote = (state) =>
  getSelectedQuote(state) || getTopQuote(state)

// Compound selectors

export const getDestinationTokenInfo = (state) =>
  getFetchParams(state)?.metaData?.destinationTokenInfo

export const getUsedSwapsGasPrice = (state) =>
  getCustomSwapsGasPrice(state) || getSwapsFallbackGasPrice(state)

export const getApproveTxParams = (state) => {
  const { approvalNeeded } = getSelectedQuote(state) || getTopQuote(state) || {}

  if (!approvalNeeded) {
    return null
  }
  const data = getSwapsState(state)?.customApproveTxData || approvalNeeded.data

  const gasPrice = getCustomSwapsGasPrice(state) || approvalNeeded.gasPrice
  return { ...approvalNeeded, gasPrice, data }
}

// Actions / action-creators

const {
  clearSwapsState,
  navigatedBackToBuildQuote,
  retriedGetQuotes,
  swapGasPriceEstimatesFetchCompleted,
  swapGasPriceEstimatesFetchStarted,
  swapGasPriceEstimatesFetchFailed,
  setAggregatorMetadata,
  setBalanceError,
  setFetchingQuotes,
  setFromToken,
  setQuotesFetchStartTime,
  setTopAssets,
  setToToken,
  swapCustomGasModalPriceEdited,
  swapCustomGasModalLimitEdited,
  retrievedFallbackSwapsGasPrice,
  swapCustomGasModalClosed,
} = actions

export {
  clearSwapsState,
  setAggregatorMetadata,
  setBalanceError,
  setFetchingQuotes,
  setFromToken as setSwapsFromToken,
  setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
  setTopAssets,
  setToToken as setSwapToToken,
  swapCustomGasModalPriceEdited,
  swapCustomGasModalLimitEdited,
  swapCustomGasModalClosed,
}

export const navigateBackToBuildQuote = (history) => {
  return async (dispatch) => {
    // TODO: Ensure any fetch in progress is cancelled
    await dispatch(resetSwapsPostFetchState())
    dispatch(navigatedBackToBuildQuote())

    history.push(BUILD_QUOTE_ROUTE)
  }
}

export const prepareForRetryGetQuotes = () => {
  return async (dispatch) => {
    // TODO: Ensure any fetch in progress is cancelled
    await dispatch(resetSwapsPostFetchState())
    dispatch(retriedGetQuotes())
  }
}

export const prepareToLeaveSwaps = () => {
  return async (dispatch) => {
    dispatch(clearSwapsState())
    await dispatch(resetBackgroundSwapsState())
  }
}

export const swapsQuoteSelected = (aggId) => {
  return (dispatch) => {
    dispatch(swapCustomGasModalLimitEdited(null))
    dispatch(setSelectedQuoteAggId(aggId))
    dispatch(setSwapsTxGasLimit(''))
  }
}

export const fetchAndSetSwapsGasPriceInfo = () => {
  return async (dispatch) => {
    const basicEstimates = await dispatch(fetchMetaSwapsGasPriceEstimates())

    if (basicEstimates?.fast) {
      dispatch(setSwapsTxGasPrice(decGWEIToHexWEI(basicEstimates.fast)))
    }
  }
}

export const fetchQuotesAndSetQuoteState = (
  history,
  inputValue,
  maxSlippage,
  metaMetricsEvent,
) => {
  return async (dispatch, getState) => {
    let swapsFeatureIsLive = false
    try {
      swapsFeatureIsLive = await fetchSwapsFeatureLiveness()
    } catch (error) {
      log.error('Failed to fetch Swaps liveness, defaulting to false.', error)
    }
    await dispatch(setSwapsLiveness(swapsFeatureIsLive))

    if (!swapsFeatureIsLive) {
      await history.push(SWAPS_MAINTENANCE_ROUTE)
      return
    }

    const state = getState()
    const fetchParams = getFetchParams(state)
    const selectedAccount = getSelectedAccount(state)
    const balanceError = getBalanceError(state)
    const fetchParamsFromToken =
      fetchParams?.metaData?.sourceTokenInfo?.symbol === 'ETH'
        ? {
            ...ETH_SWAPS_TOKEN_OBJECT,
            string: getValueFromWeiHex({
              value: selectedAccount.balance,
              numberOfDecimals: 4,
              toDenomination: 'ETH',
            }),
            balance: hexToDecimal(selectedAccount.balance),
          }
        : fetchParams?.metaData?.sourceTokenInfo
    const selectedFromToken = getFromToken(state) || fetchParamsFromToken || {}
    const selectedToToken =
      getToToken(state) || fetchParams?.metaData?.destinationTokenInfo || {}
    const {
      address: fromTokenAddress,
      symbol: fromTokenSymbol,
      decimals: fromTokenDecimals,
      iconUrl: fromTokenIconUrl,
      balance: fromTokenBalance,
    } = selectedFromToken
    const {
      address: toTokenAddress,
      symbol: toTokenSymbol,
      decimals: toTokenDecimals,
      iconUrl: toTokenIconUrl,
    } = selectedToToken
    await dispatch(setBackgroundSwapRouteState('loading'))
    history.push(LOADING_QUOTES_ROUTE)
    dispatch(setFetchingQuotes(true))

    const contractExchangeRates = getTokenExchangeRates(state)

    let destinationTokenAddedForSwap = false
    if (toTokenSymbol !== 'ETH' && !contractExchangeRates[toTokenAddress]) {
      destinationTokenAddedForSwap = true
      await dispatch(
        addToken(
          toTokenAddress,
          toTokenSymbol,
          toTokenDecimals,
          toTokenIconUrl,
          true,
        ),
      )
    }
    if (
      fromTokenSymbol !== 'ETH' &&
      !contractExchangeRates[fromTokenAddress] &&
      fromTokenBalance &&
      new BigNumber(fromTokenBalance, 16).gt(0)
    ) {
      dispatch(
        addToken(
          fromTokenAddress,
          fromTokenSymbol,
          fromTokenDecimals,
          fromTokenIconUrl,
          true,
        ),
      )
    }

    const swapsTokens = getSwapsTokens(state)

    const sourceTokenInfo =
      swapsTokens?.find(({ address }) => address === fromTokenAddress) ||
      selectedFromToken
    const destinationTokenInfo =
      swapsTokens?.find(({ address }) => address === toTokenAddress) ||
      selectedToToken

    dispatch(setFromToken(selectedFromToken))

    metaMetricsEvent({
      event: 'Quotes Requested',
      category: 'swaps',
    })
    metaMetricsEvent({
      event: 'Quotes Requested',
      category: 'swaps',
      excludeMetaMetricsId: true,
      properties: {
        token_from: fromTokenSymbol,
        token_from_amount: String(inputValue),
        token_to: toTokenSymbol,
        request_type: balanceError ? 'Quote' : 'Order',
        slippage: maxSlippage,
        custom_slippage: maxSlippage !== 2,
        anonymizedData: true,
      },
    })

    try {
      const fetchStartTime = Date.now()
      dispatch(setQuotesFetchStartTime(fetchStartTime))

      const fetchAndSetQuotesPromise = dispatch(
        fetchAndSetQuotes(
          {
            slippage: maxSlippage,
            sourceToken: fromTokenAddress,
            destinationToken: toTokenAddress,
            value: inputValue,
            fromAddress: selectedAccount.address,
            destinationTokenAddedForSwap,
            balanceError,
            sourceDecimals: fromTokenDecimals,
          },
          {
            sourceTokenInfo,
            destinationTokenInfo,
            accountBalance: selectedAccount.balance,
          },
        ),
      )

      const gasPriceFetchPromise = dispatch(fetchAndSetSwapsGasPriceInfo())

      const [[fetchedQuotes, selectedAggId]] = await Promise.all([
        fetchAndSetQuotesPromise,
        gasPriceFetchPromise,
      ])

      if (Object.values(fetchedQuotes)?.length === 0) {
        metaMetricsEvent({
          event: 'No Quotes Available',
          category: 'swaps',
        })
        metaMetricsEvent({
          event: 'No Quotes Available',
          category: 'swaps',
          excludeMetaMetricsId: true,
          properties: {
            token_from: fromTokenSymbol,
            token_from_amount: String(inputValue),
            token_to: toTokenSymbol,
            request_type: balanceError ? 'Quote' : 'Order',
            slippage: maxSlippage,
            custom_slippage: maxSlippage !== 2,
          },
        })
        dispatch(setSwapsErrorKey(QUOTES_NOT_AVAILABLE_ERROR))
      } else {
        const newSelectedQuote = fetchedQuotes[selectedAggId]

        metaMetricsEvent({
          event: 'Quotes Received',
          category: 'swaps',
        })
        metaMetricsEvent({
          event: 'Quotes Received',
          category: 'swaps',
          excludeMetaMetricsId: true,
          properties: {
            token_from: fromTokenSymbol,
            token_from_amount: String(inputValue),
            token_to: toTokenSymbol,
            token_to_amount: calcTokenAmount(
              newSelectedQuote.destinationAmount,
              newSelectedQuote.decimals || 18,
            ),
            request_type: balanceError ? 'Quote' : 'Order',
            slippage: maxSlippage,
            custom_slippage: maxSlippage !== 2,
            response_time: Date.now() - fetchStartTime,
            best_quote_source: newSelectedQuote.aggregator,
            available_quotes: Object.values(fetchedQuotes)?.length,
            anonymizedData: true,
          },
        })

        dispatch(setInitialGasEstimate(selectedAggId))
      }
    } catch (e) {
      // A newer swap request is running, so simply bail and let the newer request respond
      if (e.message === SWAPS_FETCH_ORDER_CONFLICT) {
        log.debug(`Swap fetch order conflict detected; ignoring older request`)
        return
      }
      // TODO: Check for any errors we should expect to occur in production, and report others to Sentry
      log.error(`Error fetching quotes: `, e)

      dispatch(setSwapsErrorKey(ERROR_FETCHING_QUOTES))
    }

    dispatch(setFetchingQuotes(false))
  }
}

export const signAndSendTransactions = (history, metaMetricsEvent) => {
  return async (dispatch, getState) => {
    let swapsFeatureIsLive = false
    try {
      swapsFeatureIsLive = await fetchSwapsFeatureLiveness()
    } catch (error) {
      log.error('Failed to fetch Swaps liveness, defaulting to false.', error)
    }
    await dispatch(setSwapsLiveness(swapsFeatureIsLive))

    if (!swapsFeatureIsLive) {
      await history.push(SWAPS_MAINTENANCE_ROUTE)
      return
    }

    const state = getState()
    const customSwapsGas = getCustomSwapsGas(state)
    const fetchParams = getFetchParams(state)
    const { metaData, value: swapTokenValue, slippage } = fetchParams
    const { sourceTokenInfo = {}, destinationTokenInfo = {} } = metaData
    await dispatch(setBackgroundSwapRouteState('awaiting'))
    await dispatch(stopPollingForQuotes())
    history.push(AWAITING_SWAP_ROUTE)

    const { fast: fastGasEstimate } = getSwapGasPriceEstimateData(state)

    const usedQuote = getUsedQuote(state)
    const usedTradeTxParams = usedQuote.trade

    const estimatedGasLimit = new BigNumber(
      usedQuote?.gasEstimate || decimalToHex(usedQuote?.averageGas || 0),
      16,
    )
    const estimatedGasLimitWithMultiplier = estimatedGasLimit
      .times(1.4, 10)
      .round(0)
      .toString(16)
    const maxGasLimit =
      customSwapsGas ||
      hexMax(
        `0x${decimalToHex(usedQuote?.maxGas || 0)}`,
        estimatedGasLimitWithMultiplier,
      )

    const usedGasPrice = getUsedSwapsGasPrice(state)
    usedTradeTxParams.gas = maxGasLimit
    usedTradeTxParams.gasPrice = usedGasPrice

    const usdConversionRate = getUSDConversionRate(state)
    const destinationValue = calcTokenAmount(
      usedQuote.destinationAmount,
      destinationTokenInfo.decimals || 18,
    ).toPrecision(8)
    const usedGasLimitEstimate =
      usedQuote?.gasEstimateWithRefund ||
      `0x${decimalToHex(usedQuote?.averageGas || 0)}`
    const totalGasLimitEstimate = new BigNumber(usedGasLimitEstimate, 16)
      .plus(usedQuote.approvalNeeded?.gas || '0x0', 16)
      .toString(16)
    const gasEstimateTotalInUSD = getValueFromWeiHex({
      value: calcGasTotal(totalGasLimitEstimate, usedGasPrice),
      toCurrency: 'usd',
      conversionRate: usdConversionRate,
      numberOfDecimals: 6,
    })

    const swapMetaData = {
      token_from: sourceTokenInfo.symbol,
      token_from_amount: String(swapTokenValue),
      token_to: destinationTokenInfo.symbol,
      token_to_amount: destinationValue,
      slippage,
      custom_slippage: slippage !== 2,
      best_quote_source: getTopQuote(state)?.aggregator,
      available_quotes: getQuotes(state)?.length,
      other_quote_selected:
        usedQuote.aggregator !== getTopQuote(state)?.aggregator,
      other_quote_selected_source:
        usedQuote.aggregator === getTopQuote(state)?.aggregator
          ? ''
          : usedQuote.aggregator,
      gas_fees: gasEstimateTotalInUSD,
      estimated_gas: estimatedGasLimit.toString(10),
      suggested_gas_price: fastGasEstimate,
      used_gas_price: hexWEIToDecGWEI(usedGasPrice),
      average_savings: usedQuote.savings?.total,
      performance_savings: usedQuote.savings?.performance,
      fee_savings: usedQuote.savings?.fee,
      median_metamask_fee: usedQuote.savings?.medianMetaMaskFee,
    }

    const metaMetricsConfig = {
      event: 'Swap Started',
      category: 'swaps',
    }

    metaMetricsEvent({ ...metaMetricsConfig })
    metaMetricsEvent({
      ...metaMetricsConfig,
      excludeMetaMetricsId: true,
      properties: swapMetaData,
    })

    let finalApproveTxMeta
    const approveTxParams = getApproveTxParams(state)
    if (approveTxParams) {
      const approveTxMeta = await dispatch(
        addUnapprovedTransaction(
          { ...approveTxParams, amount: '0x0' },
          'metamask',
        ),
      )
      await dispatch(setApproveTxId(approveTxMeta.id))
      finalApproveTxMeta = await dispatch(
        updateTransaction(
          {
            ...approveTxMeta,
            transactionCategory: TRANSACTION_CATEGORIES.SWAP_APPROVAL,
            sourceTokenSymbol: sourceTokenInfo.symbol,
          },
          true,
        ),
      )
      try {
        await dispatch(updateAndApproveTx(finalApproveTxMeta, true))
      } catch (e) {
        await dispatch(setSwapsErrorKey(SWAP_FAILED_ERROR))
        history.push(SWAPS_ERROR_ROUTE)
        return
      }
    }

    const tradeTxMeta = await dispatch(
      addUnapprovedTransaction(usedTradeTxParams, 'metamask'),
    )
    dispatch(setTradeTxId(tradeTxMeta.id))
    const finalTradeTxMeta = await dispatch(
      updateTransaction(
        {
          ...tradeTxMeta,
          sourceTokenSymbol: sourceTokenInfo.symbol,
          destinationTokenSymbol: destinationTokenInfo.symbol,
          transactionCategory: TRANSACTION_CATEGORIES.SWAP,
          destinationTokenDecimals: destinationTokenInfo.decimals,
          destinationTokenAddress: destinationTokenInfo.address,
          swapMetaData,
          swapTokenValue,
          approvalTxId: finalApproveTxMeta?.id,
        },
        true,
      ),
    )
    try {
      await dispatch(updateAndApproveTx(finalTradeTxMeta, true))
    } catch (e) {
      await dispatch(setSwapsErrorKey(SWAP_FAILED_ERROR))
      history.push(SWAPS_ERROR_ROUTE)
      return
    }

    await forceUpdateMetamaskState(dispatch)
  }
}

export function fetchMetaSwapsGasPriceEstimates() {
  return async (dispatch, getState) => {
    const state = getState()
    const priceEstimatesLastRetrieved = getSwapsPriceEstimatesLastRetrieved(
      state,
    )
    const timeLastRetrieved =
      priceEstimatesLastRetrieved ||
      loadLocalStorageData('METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED') ||
      0

    dispatch(swapGasPriceEstimatesFetchStarted())

    let priceEstimates
    try {
      if (Date.now() - timeLastRetrieved > 30000) {
        priceEstimates = await fetchSwapsGasPrices()
      } else {
        const cachedPriceEstimates = loadLocalStorageData(
          'METASWAP_GAS_PRICE_ESTIMATES',
        )
        priceEstimates = cachedPriceEstimates || (await fetchSwapsGasPrices())
      }
    } catch (e) {
      log.warn('Fetching swaps gas prices failed:', e)

      if (!e.message?.match(/NetworkError|Fetch failed with status:/u)) {
        throw e
      }

      dispatch(swapGasPriceEstimatesFetchFailed())

      try {
        const gasPrice = await global.ethQuery.gasPrice()
        const gasPriceInDecGWEI = hexWEIToDecGWEI(gasPrice.toString(10))

        dispatch(retrievedFallbackSwapsGasPrice(gasPriceInDecGWEI))
        return null
      } catch (networkGasPriceError) {
        console.error(
          `Failed to retrieve fallback gas price: `,
          networkGasPriceError,
        )
        return null
      }
    }

    const timeRetrieved = Date.now()

    saveLocalStorageData(priceEstimates, 'METASWAP_GAS_PRICE_ESTIMATES')
    saveLocalStorageData(
      timeRetrieved,
      'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED',
    )

    dispatch(
      swapGasPriceEstimatesFetchCompleted({
        priceEstimates,
        priceEstimatesLastRetrieved: timeRetrieved,
      }),
    )
    return priceEstimates
  }
}
