import { useWeb3React } from '@web3-react/core'
import React, { createContext, useCallback, useContext, useEffect, useMemo, useReducer } from 'react'
import * as ls from 'local-storage'
import { utils } from 'ethers'
import { getTokenDecimals, getTokenName, getTokenSymbol, isAddress, safeAccess } from '../utils'
import { DEFAULT_TOKENS_EXTRA, DISABLED_TOKENS } from './DefaultTokens'
import { NATIVE_TOKEN_TICKER, ChainId } from '../constants/networks'

const NAME = 'name'
const SYMBOL = 'symbol'
const DECIMALS = 'decimals'
const EXCHANGE_ADDRESS = 'exchangeAddress'

// the Uniswap Default token list lives here
export const DEFAULT_TOKEN_LIST_URL = {
  ETH: 'https://unpkg.com/@uniswap/default-token-list@latest',
  MATIC: 'https://unpkg.com/[email protected]/build/quickswap-default.tokenlist.json',
}

const UPDATE = 'UPDATE'
const SET_LIST = 'SET_LIST'

const CHAIN_TOKENS = {
  ETH: {
    ETH: {
      [NAME]: 'Ethereum',
      [SYMBOL]: 'ETH',
      [DECIMALS]: 18,
      [EXCHANGE_ADDRESS]: null,
    },
  },
  MATIC: {
    MATIC: {
      [NAME]: 'Matic',
      [SYMBOL]: 'MATIC',
      [DECIMALS]: 18,
      [EXCHANGE_ADDRESS]: null,
    },
  },
}

export const WETH = {
  1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
  3: '0xc778417e063141139fce010982780140aa0cd5ab',
  137: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
}

const EMPTY_LIST = {
  [ChainId.MATIC]: {},
  [ChainId.ROPSTEN]: {},
  [ChainId.MAINNET]: {},
}

const TokensContext = createContext()

function useTokensContext() {
  return useContext(TokensContext)
}

function reducer(state, { type, payload }) {
  switch (type) {
    case UPDATE: {
      const { chainId, tokenAddress, name, symbol, decimals } = payload
      return {
        ...state,
        [chainId]: {
          ...(safeAccess(state, [chainId]) || {}),
          [tokenAddress]: {
            [NAME]: name,
            [SYMBOL]: symbol,
            [DECIMALS]: decimals,
          },
        },
      }
    }
    case SET_LIST: {
      return payload
    }
    default: {
      throw Error(`Unexpected action type in TokensContext reducer: '${type}'.`)
    }
  }
}

export default function Provider({ children }) {
  const [state, dispatch] = useReducer(reducer, EMPTY_LIST)
  const { chainId } = useWeb3React()
  useEffect(() => {
    fetch(DEFAULT_TOKEN_LIST_URL[NATIVE_TOKEN_TICKER[chainId]])
      .then((res) =>
        res.json().then((list) => {
          const tokenList = list.tokens
            .filter((token) => !DISABLED_TOKENS[token.symbol])
            .concat(DEFAULT_TOKENS_EXTRA)
            .reduce(
              (tokenMap, token) => {
                try {
                  const tokenAddress = utils.getAddress(token.address)
                  if (tokenMap[token.chainId][tokenAddress] !== undefined) {
                    console.warn('Duplicate tokens.')
                    return tokenMap
                  }

                  return {
                    ...tokenMap,
                    [token.chainId]: {
                      ...tokenMap[token.chainId],
                      [tokenAddress]: token,
                    },
                  }
                } catch (error) {
                  return {
                    ...tokenMap,
                  }
                }
              },
              { ...EMPTY_LIST }
            )

          dispatch({ type: SET_LIST, payload: tokenList })
        })
      )
      .catch((e) => console.error(e.message))
  }, [chainId])

  const update = useCallback((chainId, tokenAddress, name, symbol, decimals) => {
    dispatch({ type: UPDATE, payload: { chainId, tokenAddress, name, symbol, decimals } })
  }, [])

  return (
    <TokensContext.Provider value={useMemo(() => [state, { update }], [state, update])}>
      {children}
    </TokensContext.Provider>
  )
}

export function useTokenDetails(tokenAddress) {
  const { chainId, library } = useWeb3React()
  const [state, { update }] = useTokensContext()
  const allTokensInNetwork = { ...CHAIN_TOKENS[NATIVE_TOKEN_TICKER[chainId]], ...(safeAccess(state, [chainId]) || {}) }
  const { [NAME]: name, [SYMBOL]: symbol, [DECIMALS]: decimals } = safeAccess(allTokensInNetwork, [tokenAddress]) || {}

  useEffect(() => {
    if (
      isAddress(tokenAddress) &&
      (name === undefined || symbol === undefined || decimals === undefined) &&
      (chainId || chainId === 0) &&
      library
    ) {
      let stale = false

      const namePromise = getTokenName(tokenAddress, library).catch(() => null)
      const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
      const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null)

      Promise.all([namePromise, symbolPromise, decimalsPromise]).then(
        ([resolvedName, resolvedSymbol, resolvedDecimals]) => {
          if (!stale) {
            update(chainId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals)
          }
        }
      )
      return () => {
        stale = true
      }
    }
  }, [tokenAddress, name, symbol, decimals, chainId, library, update])

  return { name, symbol, decimals, chainId }
}

export function useAllTokenDetails(r) {
  const { chainId } = useWeb3React()

  const [state] = useTokensContext()
  const tokenDetails = { ...CHAIN_TOKENS[NATIVE_TOKEN_TICKER[chainId]], ...(safeAccess(state, [chainId]) || {}) }

  return tokenDetails
}