import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { getTokenTrackerLink } from '@metamask/etherscan-link';
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
import {
  checkExistingAddresses,
  getURLHostName,
} from '../../helpers/utils/util';
import { tokenInfoGetter } from '../../helpers/utils/token-util';
import {
  ADD_NFT_ROUTE,
  CONFIRM_IMPORT_TOKEN_ROUTE,
  SECURITY_ROUTE,
} from '../../helpers/constants/routes';
import TextField from '../../components/ui/text-field';
import PageContainer from '../../components/ui/page-container';
import { Tabs, Tab } from '../../components/ui/tabs';
import { addHexPrefix } from '../../../app/scripts/lib/util';
import { isValidHexAddress } from '../../../shared/modules/hexstring-utils';
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
import Typography from '../../components/ui/typography';
import {
  TypographyVariant,
  FONT_WEIGHT,
} from '../../helpers/constants/design-system';
import Button from '../../components/ui/button';
import { TokenStandard } from '../../../shared/constants/transaction';
import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens';
import TokenSearch from './token-search';
import TokenList from './token-list';

const emptyAddr = '0x0000000000000000000000000000000000000000';

const MIN_DECIMAL_VALUE = 0;
const MAX_DECIMAL_VALUE = 36;

class ImportToken extends Component {
  static contextTypes = {
    t: PropTypes.func,
  };

  static propTypes = {
    /**
     * History object of the router.
     */
    history: PropTypes.object,

    /**
     * Set the state of `pendingTokens`, called when adding a token.
     */
    setPendingTokens: PropTypes.func,

    /**
     * The current list of pending tokens to be added.
     */
    pendingTokens: PropTypes.object,

    /**
     * Clear the list of pending tokens. Called when closing the modal.
     */
    clearPendingTokens: PropTypes.func,

    /**
     * The list of already added tokens.
     */
    tokens: PropTypes.array,

    /**
     * The identities/accounts that are currently added to the wallet.
     */
    identities: PropTypes.object,

    /**
     * Boolean flag that shows/hides the search tab.
     */
    showSearchTab: PropTypes.bool.isRequired,

    /**
     * The most recent overview page route, which is 'navigated' to when closing the modal.
     */
    mostRecentOverviewPage: PropTypes.string.isRequired,

    /**
     * The active chainId in use.
     */
    chainId: PropTypes.string,

    /**
     * The rpc preferences to use for the current provider.
     */
    rpcPrefs: PropTypes.object,

    /**
     * The list of tokens available for search.
     */
    tokenList: PropTypes.object,

    /**
     * Boolean flag indicating whether token detection is enabled or not.
     * When disabled, shows an information alert in the search tab informing the
     * user of the availability of this feature.
     */
    useTokenDetection: PropTypes.bool,

    /**
     * Function called to fetch information about the token standard and
     * details, see `actions.js`.
     */
    getTokenStandardAndDetails: PropTypes.func,

    /**
     * The currently selected active address.
     */
    selectedAddress: PropTypes.string,
    isDynamicTokenListAvailable: PropTypes.bool.isRequired,
    tokenDetectionInactiveOnNonMainnetSupportedNetwork:
      PropTypes.bool.isRequired,
    networkName: PropTypes.string.isRequired,
  };

  static defaultProps = {
    tokenList: {},
  };

  state = {
    customAddress: '',
    customSymbol: '',
    customDecimals: 0,
    searchResults: [],
    selectedTokens: {},
    standard: TokenStandard.NONE,
    tokenSelectorError: null,
    customAddressError: null,
    customSymbolError: null,
    customDecimalsError: null,
    nftAddressError: null,
    forceEditSymbol: false,
    symbolAutoFilled: false,
    decimalAutoFilled: false,
    mainnetTokenWarning: null,
  };

  componentDidMount() {
    this.tokenInfoGetter = tokenInfoGetter();
    const { pendingTokens = {} } = this.props;
    const pendingTokenKeys = Object.keys(pendingTokens);

    if (pendingTokenKeys.length > 0) {
      let selectedTokens = {};
      let customToken = {};

      pendingTokenKeys.forEach((tokenAddress) => {
        const token = pendingTokens[tokenAddress];
        const { isCustom } = token;

        if (isCustom) {
          customToken = { ...token };
        } else {
          selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } };
        }
      });

      const {
        address: customAddress = '',
        symbol: customSymbol = '',
        decimals: customDecimals = 0,
      } = customToken;

      this.setState({
        selectedTokens,
        customAddress,
        customSymbol,
        customDecimals,
      });
    }
  }

  handleToggleToken(token) {
    const { address } = token;
    const { selectedTokens = {} } = this.state;
    const selectedTokensCopy = { ...selectedTokens };

    if (address in selectedTokensCopy) {
      delete selectedTokensCopy[address];
    } else {
      selectedTokensCopy[address] = token;
    }

    this.setState({
      selectedTokens: selectedTokensCopy,
      tokenSelectorError: null,
    });
  }

  hasError() {
    const {
      tokenSelectorError,
      customAddressError,
      customSymbolError,
      customDecimalsError,
      nftAddressError,
    } = this.state;

    return (
      tokenSelectorError ||
      customAddressError ||
      customSymbolError ||
      customDecimalsError ||
      nftAddressError
    );
  }

  hasSelected() {
    const { customAddress = '', selectedTokens = {} } = this.state;
    return customAddress || Object.keys(selectedTokens).length > 0;
  }

  handleNext() {
    if (this.hasError()) {
      return;
    }

    if (!this.hasSelected()) {
      this.setState({ tokenSelectorError: this.context.t('mustSelectOne') });
      return;
    }

    const { setPendingTokens, history, tokenList } = this.props;
    const tokenAddressList = Object.keys(tokenList);
    const {
      customAddress: address,
      customSymbol: symbol,
      customDecimals: decimals,
      selectedTokens,
      standard,
    } = this.state;

    const customToken = {
      address,
      symbol,
      decimals,
      standard,
    };

    setPendingTokens({ customToken, selectedTokens, tokenAddressList });
    history.push(CONFIRM_IMPORT_TOKEN_ROUTE);
  }

  async attemptToAutoFillTokenParams(address) {
    const { tokenList } = this.props;
    const { symbol = '', decimals } = await this.tokenInfoGetter(
      address,
      tokenList,
    );

    const symbolAutoFilled = Boolean(symbol);
    const decimalAutoFilled = Boolean(decimals);
    this.setState({ symbolAutoFilled, decimalAutoFilled });
    this.handleCustomSymbolChange(symbol || '');
    this.handleCustomDecimalsChange(decimals);
  }

  async handleCustomAddressChange(value) {
    const customAddress = value.trim();
    this.setState({
      customAddress,
      customAddressError: null,
      nftAddressError: null,
      tokenSelectorError: null,
      symbolAutoFilled: false,
      decimalAutoFilled: false,
      mainnetTokenWarning: null,
    });

    const addressIsValid = isValidHexAddress(customAddress, {
      allowNonPrefixed: false,
    });
    const standardAddress = addHexPrefix(customAddress).toLowerCase();

    const isMainnetToken = Object.keys(STATIC_MAINNET_TOKEN_LIST).some(
      (key) => key.toLowerCase() === customAddress.toLowerCase(),
    );

    const isMainnetNetwork = this.props.chainId === '0x1';

    let standard;
    if (addressIsValid) {
      try {
        ({ standard } = await this.props.getTokenStandardAndDetails(
          standardAddress,
          this.props.selectedAddress,
        ));
      } catch (error) {
        // ignore
      }
    }

    const addressIsEmpty =
      customAddress.length === 0 || customAddress === emptyAddr;

    switch (true) {
      case !addressIsValid && !addressIsEmpty:
        this.setState({
          customAddressError: this.context.t('invalidAddress'),
          customSymbol: '',
          customDecimals: 0,
          customSymbolError: null,
          customDecimalsError: null,
        });

        break;
      case process.env.NFTS_V1 &&
        (standard === 'ERC1155' || standard === 'ERC721'):
        this.setState({
          nftAddressError: this.context.t('nftAddressError', [
            <a
              className="import-token__nft-address-error-link"
              onClick={() =>
                this.props.history.push({
                  pathname: ADD_NFT_ROUTE,
                  state: {
                    addressEnteredOnImportTokensPage: this.state.customAddress,
                  },
                })
              }
              key="nftAddressError"
            >
              {this.context.t('importNFTPage')}
            </a>,
          ]),
        });

        break;
      case isMainnetToken && !isMainnetNetwork:
        this.setState({
          mainnetTokenWarning: this.context.t('mainnetToken'),
          customSymbol: '',
          customDecimals: 0,
          customSymbolError: null,
          customDecimalsError: null,
        });

        break;
      case Boolean(this.props.identities[standardAddress]):
        this.setState({
          customAddressError: this.context.t('personalAddressDetected'),
        });

        break;
      case checkExistingAddresses(customAddress, this.props.tokens):
        this.setState({
          customAddressError: this.context.t('tokenAlreadyAdded'),
        });

        break;
      default:
        if (!addressIsEmpty) {
          this.attemptToAutoFillTokenParams(customAddress);
          if (standard) {
            this.setState({ standard });
          }
        }
    }
  }

  handleCustomSymbolChange(value) {
    const customSymbol = value.trim();
    const symbolLength = customSymbol.length;
    let customSymbolError = null;

    if (symbolLength <= 0 || symbolLength >= 12) {
      customSymbolError = this.context.t('symbolBetweenZeroTwelve');
    }

    this.setState({ customSymbol, customSymbolError });
  }

  handleCustomDecimalsChange(value) {
    let customDecimals;
    let customDecimalsError = null;

    if (value) {
      customDecimals = Number(value.trim());
      customDecimalsError =
        value < MIN_DECIMAL_VALUE || value > MAX_DECIMAL_VALUE
          ? this.context.t('decimalsMustZerotoTen')
          : null;
    } else {
      customDecimals = '';
      customDecimalsError = this.context.t('tokenDecimalFetchFailed');
    }

    this.setState({ customDecimals, customDecimalsError });
  }

  renderCustomTokenForm() {
    const { t } = this.context;
    const {
      customAddress,
      customSymbol,
      customDecimals,
      customAddressError,
      customSymbolError,
      customDecimalsError,
      forceEditSymbol,
      symbolAutoFilled,
      decimalAutoFilled,
      mainnetTokenWarning,
      nftAddressError,
    } = this.state;

    const {
      chainId,
      rpcPrefs,
      isDynamicTokenListAvailable,
      tokenDetectionInactiveOnNonMainnetSupportedNetwork,
      history,
    } = this.props;
    const blockExplorerTokenLink = getTokenTrackerLink(
      customAddress,
      chainId,
      null,
      null,
      { blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null },
    );
    const blockExplorerLabel = rpcPrefs?.blockExplorerUrl
      ? getURLHostName(blockExplorerTokenLink)
      : t('etherscan');

    return (
      <div className="import-token__custom-token-form">
        {tokenDetectionInactiveOnNonMainnetSupportedNetwork ? (
          <ActionableMessage
            type="warning"
            message={t('customTokenWarningInTokenDetectionNetworkWithTDOFF', [
              <Button
                type="link"
                key="import-token-security-risk"
                className="import-token__link"
                rel="noopener noreferrer"
                target="_blank"
                href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
              >
                {t('tokenScamSecurityRisk')}
              </Button>,
              <Button
                type="link"
                key="import-token-token-detection-announcement"
                className="import-token__link"
                onClick={() =>
                  history.push(`${SECURITY_ROUTE}#token-description`)
                }
              >
                {t('inYourSettings')}
              </Button>,
            ])}
            withRightButton
            useIcon
            iconFillColor="var(--color-warning-default)"
          />
        ) : (
          <ActionableMessage
            type={isDynamicTokenListAvailable ? 'warning' : 'default'}
            message={t(
              isDynamicTokenListAvailable
                ? 'customTokenWarningInTokenDetectionNetwork'
                : 'customTokenWarningInNonTokenDetectionNetwork',
              [
                <Button
                  type="link"
                  key="import-token-fake-token-warning"
                  className="import-token__link"
                  rel="noopener noreferrer"
                  target="_blank"
                  href={ZENDESK_URLS.TOKEN_SAFETY_PRACTICES}
                >
                  {t('learnScamRisk')}
                </Button>,
              ],
            )}
            withRightButton
            useIcon
            iconFillColor={
              isDynamicTokenListAvailable
                ? 'var(--color-warning-default)'
                : 'var(--color-info-default)'
            }
          />
        )}
        <TextField
          id="custom-address"
          label={t('tokenContractAddress')}
          type="text"
          value={customAddress}
          onChange={(e) => this.handleCustomAddressChange(e.target.value)}
          error={customAddressError || mainnetTokenWarning || nftAddressError}
          fullWidth
          autoFocus
          margin="normal"
        />
        <TextField
          id="custom-symbol"
          label={
            <div className="import-token__custom-symbol__label-wrapper">
              <span className="import-token__custom-symbol__label">
                {t('tokenSymbol')}
              </span>
              {symbolAutoFilled && !forceEditSymbol && (
                <div
                  className="import-token__custom-symbol__edit"
                  onClick={() => this.setState({ forceEditSymbol: true })}
                >
                  {t('edit')}
                </div>
              )}
            </div>
          }
          type="text"
          value={customSymbol}
          onChange={(e) => this.handleCustomSymbolChange(e.target.value)}
          error={customSymbolError}
          fullWidth
          margin="normal"
          disabled={symbolAutoFilled && !forceEditSymbol}
        />
        <TextField
          id="custom-decimals"
          label={t('decimal')}
          type="number"
          value={customDecimals}
          onChange={(e) => this.handleCustomDecimalsChange(e.target.value)}
          error={customDecimals ? customDecimalsError : null}
          fullWidth
          margin="normal"
          disabled={decimalAutoFilled}
          min={MIN_DECIMAL_VALUE}
          max={MAX_DECIMAL_VALUE}
        />
        {customDecimals === '' && (
          <ActionableMessage
            message={
              <>
                <Typography
                  variant={TypographyVariant.H7}
                  fontWeight={FONT_WEIGHT.BOLD}
                >
                  {t('tokenDecimalFetchFailed')}
                </Typography>
                <Typography
                  variant={TypographyVariant.H7}
                  fontWeight={FONT_WEIGHT.NORMAL}
                >
                  {t('verifyThisTokenDecimalOn', [
                    <Button
                      type="link"
                      key="import-token-verify-token-decimal"
                      className="import-token__link"
                      rel="noopener noreferrer"
                      target="_blank"
                      href={blockExplorerTokenLink}
                    >
                      {blockExplorerLabel}
                    </Button>,
                  ])}
                </Typography>
              </>
            }
            type="warning"
            withRightButton
            className="import-token__decimal-warning"
          />
        )}
      </div>
    );
  }

  renderSearchToken() {
    const { t } = this.context;
    const { tokenList, history, useTokenDetection, networkName } = this.props;
    const { tokenSelectorError, selectedTokens, searchResults } = this.state;
    return (
      <div className="import-token__search-token">
        {!useTokenDetection && (
          <ActionableMessage
            message={t('enhancedTokenDetectionAlertMessage', [
              networkName,
              <Button
                type="link"
                key="token-detection-announcement"
                className="import-token__link"
                onClick={() =>
                  history.push(`${SECURITY_ROUTE}#token-description`)
                }
              >
                {t('enableFromSettings')}
              </Button>,
            ])}
            withRightButton
            useIcon
            iconFillColor="var(--color-primary-default)"
            className="import-token__token-detection-announcement"
          />
        )}
        <TokenSearch
          onSearch={({ results = [] }) =>
            this.setState({ searchResults: results })
          }
          error={tokenSelectorError}
          tokenList={tokenList}
        />
        <div className="import-token__token-list">
          <TokenList
            results={searchResults}
            selectedTokens={selectedTokens}
            onToggleToken={(token) => this.handleToggleToken(token)}
          />
        </div>
      </div>
    );
  }

  renderTabs() {
    const { t } = this.context;
    const { showSearchTab } = this.props;
    const tabs = [];

    if (showSearchTab) {
      tabs.push(
        <Tab name={t('search')} key="search-tab" tabKey="search">
          {this.renderSearchToken()}
        </Tab>,
      );
    }
    tabs.push(
      <Tab name={t('customToken')} key="custom-tab" tabKey="customToken">
        {this.renderCustomTokenForm()}
      </Tab>,
    );

    return <Tabs>{tabs}</Tabs>;
  }

  render() {
    const { history, clearPendingTokens, mostRecentOverviewPage } = this.props;

    return (
      <PageContainer
        title={this.context.t('importTokensCamelCase')}
        tabsComponent={this.renderTabs()}
        onSubmit={() => this.handleNext()}
        hideCancel
        disabled={Boolean(this.hasError()) || !this.hasSelected()}
        onClose={() => {
          clearPendingTokens();
          history.push(mostRecentOverviewPage);
        }}
      />
    );
  }
}

export default ImportToken;