import { TChangeStepSwap, TInitSwap, TChangeSwapProvider, ProviderName } from 'actions/swap';
import {
  NormalizedBityRates,
  NormalizedShapeshiftRates,
  NormalizedOptions,
  SwapInput
} from 'reducers/swap/types';
import SimpleButton from 'components/ui/SimpleButton';
import { generateKindMax, generateKindMin, WhitelistedCoins, bityConfig } from 'config/bity';
import React, { PureComponent } from 'react';
import translate from 'translations';
import { combineAndUpper } from 'utils/formatters';
import { SwapDropdown, Input } from 'components/ui';
import Spinner from 'components/ui/Spinner';
import { merge, reject, debounce } from 'lodash';
import './CurrencySwap.scss';

export interface StateProps {
  bityRates: NormalizedBityRates;
  shapeshiftRates: NormalizedShapeshiftRates;
  provider: ProviderName;
  options: NormalizedOptions;
}

export interface ActionProps {
  changeStepSwap: TChangeStepSwap;
  initSwap: TInitSwap;
  swapProvider: TChangeSwapProvider;
}

interface State {
  disabled: boolean;
  origin: SwapInput;
  destination: SwapInput;
  originKindOptions: any[];
  destinationKindOptions: any[];
  originErr: string;
  destinationErr: string;
  timeout: boolean;
}

type Props = StateProps & ActionProps;

interface SwapOpt extends SwapInput {
  value: string;
  status: string;
  image: string;
  amount: number;
}

export default class CurrencySwap extends PureComponent<Props, State> {
  public state = {
    disabled: true,
    origin: {
      label: 'BTC',
      value: 'Bitcoin',
      status: 'available',
      image: 'https://shapeshift.io/images/coins/bitcoin.png',
      amount: NaN
    } as SwapOpt,
    destination: {
      label: 'ETH',
      value: 'Ether',
      status: 'available',
      image: 'https://shapeshift.io/images/coins/ether.png',
      amount: NaN
    } as SwapOpt,
    originKindOptions: [],
    destinationKindOptions: [],
    originErr: '',
    destinationErr: '',
    timeout: false
  };

  public debouncedCreateErrString = debounce((origin, destination, showError) => {
    const createErrString = (
      originKind: WhitelistedCoins,
      amount: number,
      destKind: WhitelistedCoins
    ) => {
      const rate = this.getMinMax(originKind, destKind);
      let errString;
      if (amount > rate.max) {
        errString = `Maximum ${rate.max} ${originKind}`;
      } else {
        errString = `Minimum ${rate.min} ${originKind}`;
      }
      return errString;
    };
    const originErr = showError
      ? createErrString(origin.label, origin.amount, destination.label)
      : '';
    const destinationErr = showError
      ? createErrString(destination.label, destination.amount, origin.label)
      : '';
    this.setErrorMessages(originErr, destinationErr);
  }, 1000);

  private timeoutId: NodeJS.Timer | null;
  public componentDidMount() {
    this.timeoutId = setTimeout(() => {
      this.setState({
        timeout: true
      });
    }, 10000);

    const { origin } = this.state;
    const { options } = this.props;

    if (options.allIds && options.byId) {
      const originKindOptions: any[] = Object.values(options.byId);
      const destinationKindOptions: any[] = Object.values(
        reject<any>(options.byId, o => o.id === origin.label)
      );

      this.setState({
        originKindOptions,
        destinationKindOptions
      });
    }
  }

  public componentWillUnmount() {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
  }

  public componentDidUpdate(prevProps: Props, prevState: State) {
    const { origin, destination } = this.state;
    const { options } = this.props;
    if (origin !== prevState.origin) {
      this.setDisabled(origin, destination);
    }

    if (options.allIds !== prevProps.options.allIds && options.byId) {
      const originKindOptions: any[] = Object.values(options.byId);
      const destinationKindOptions: any[] = Object.values(
        reject<any>(options.byId, o => o.id === origin.label)
      );

      this.setState({
        originKindOptions,
        destinationKindOptions
      });
    }
  }

  public rateMixer = () => {
    const { shapeshiftRates, bityRates } = this.props;
    return merge(shapeshiftRates, bityRates);
  };

  public getMinMax = (originKind: WhitelistedCoins, destinationKind) => {
    let min;
    let max;

    const { provider, bityRates } = this.props;

    if (provider === 'bity' && bityRates.allIds.length > 2) {
      if (originKind !== 'BTC') {
        const pairRate = this.rateMixer().byId['BTC' + originKind].rate;
        min = generateKindMin(pairRate, originKind);
        max = generateKindMax(pairRate, originKind);
      } else {
        min = bityConfig.BTCMin;
        max = bityConfig.BTCMax;
      }
    } else {
      const pair = (this.rateMixer() as NormalizedShapeshiftRates).byId[
        originKind + destinationKind
      ];
      min = pair.min;
      max = pair.limit;
    }
    return { min, max };
  };

  public isMinMaxValid = (originAmount: number, originKind: WhitelistedCoins, destinationKind) => {
    const rate = this.getMinMax(originKind, destinationKind);
    const higherThanMin = originAmount >= rate.min;
    const lowerThanMax = originAmount <= rate.max;
    return higherThanMin && lowerThanMax;
  };

  public setDisabled(origin: SwapInput, destination: SwapInput) {
    this.clearErrMessages();
    const amountsValid = origin.amount && destination.amount;
    const minMaxValid = this.isMinMaxValid(
      origin.amount as number,
      origin.label,
      destination.label
    );
    const disabled = !(amountsValid && minMaxValid);
    const showError = disabled && amountsValid;

    this.setState({
      disabled
    });

    this.debouncedCreateErrString(origin, destination, showError);
  }

  public setErrorMessages = (originErr, destinationErr) => {
    this.setState({
      originErr,
      destinationErr
    });
  };

  public clearErrMessages = () => {
    this.setState({
      originErr: '',
      destinationErr: ''
    });
  };

  public onClickStartSwap = () => {
    const { origin, destination } = this.state;
    const { changeStepSwap, initSwap } = this.props;
    initSwap({ origin, destination });
    changeStepSwap(2);
  };

  public setOriginAndDestinationToInitialVal = () => {
    this.setState({
      origin: { ...this.state.origin, amount: NaN },
      destination: { ...this.state.destination, amount: NaN }
    });
  };

  public updateOriginAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
    if (amount || amount === 0) {
      const pairName = combineAndUpper(origin.label, destination.label);
      const rate = this.rateMixer().byId[pairName].rate;
      const destinationAmount = amount * rate;
      this.setState({
        origin: { ...this.state.origin, amount },
        destination: { ...this.state.destination, amount: destinationAmount }
      });
    } else {
      this.setOriginAndDestinationToInitialVal();
    }
  };

  public updateDestinationAmount = (origin: SwapInput, destination: SwapInput, amount: number) => {
    if (amount || amount === 0) {
      const pairNameReversed = combineAndUpper(destination.label, origin.label);
      const rate = this.rateMixer().byId[pairNameReversed].rate;
      const originAmount = amount * rate;
      this.setState({
        origin: { ...this.state.origin, amount: originAmount },
        destination: {
          ...this.state.destination,
          amount
        }
      });
    } else {
      this.setOriginAndDestinationToInitialVal();
    }
  };

  public onChangeAmount = (event: React.FormEvent<HTMLInputElement>) => {
    const type = event.currentTarget.id;
    const { origin, destination } = this.state;
    const amount = parseFloat(event.currentTarget.value);
    type === 'origin-swap-input'
      ? this.updateOriginAmount(origin, destination, amount)
      : this.updateDestinationAmount(origin, destination, amount);
  };

  public onChangeOriginKind = newOption => {
    const { origin, destination, destinationKindOptions } = this.state;
    const { options, initSwap } = this.props;

    const newOrigin = { ...origin, label: newOption.label, value: newOption.value, amount: '' };
    const newDest = {
      label: newOption.label === destination.label ? origin.label : destination.label,
      value: newOption.value === destination.value ? origin.value : destination.value,
      amount: ''
    };

    this.setState({
      origin: newOrigin,
      destination: newDest,
      destinationKindOptions: reject(
        [...destinationKindOptions, options.byId[origin.label]],
        o => o.id === newOption.label
      )
    });

    initSwap({ origin: newOrigin, destination: newDest });
  };

  public onChangeDestinationKind = newOption => {
    const { initSwap } = this.props;
    const { origin, destination } = this.state;

    const newOrigin = {
      ...origin,
      amount: ''
    };

    const newDest = { ...destination, label: newOption.label, value: newOption.value, amount: '' };
    this.setState({
      origin: newOrigin,
      destination: newDest
    });

    initSwap({ origin: newOrigin, destination: newDest });
  };

  public render() {
    const { bityRates, shapeshiftRates, provider } = this.props;
    const {
      origin,
      destination,
      originKindOptions,
      destinationKindOptions,
      originErr,
      destinationErr,
      timeout
    } = this.state;
    const pairName = combineAndUpper(origin.label, destination.label);
    const bityLoaded = bityRates.byId && bityRates.byId[pairName] ? true : false;
    const shapeshiftLoaded = shapeshiftRates.byId && shapeshiftRates.byId[pairName] ? true : false;
    // This ensures both are loaded
    const loaded = provider === 'shapeshift' ? shapeshiftLoaded : bityLoaded && shapeshiftLoaded;
    const timeoutLoaded = (bityLoaded && timeout) || (shapeshiftLoaded && timeout);
    return (
      <article className="CurrencySwap">
        {loaded || timeoutLoaded ? (
          <React.Fragment>
            <div className="CurrencySwap-inner-wrap">
              <div className="flex-spacer" />
              <div className="input-group-wrapper">
                <div className="input-group-header">Deposit</div>
                <label className="input-group input-group-inline-dropdown">
                  <Input
                    id="origin-swap-input"
                    className={`input-group-input ${
                      String(origin.amount) !== '' &&
                      this.isMinMaxValid(origin.amount, origin.label, destination.label)
                        ? ''
                        : 'invalid'
                    }`}
                    type="number"
                    placeholder="Amount"
                    value={isNaN(origin.amount) ? '' : origin.amount}
                    onChange={this.onChangeAmount}
                  />
                  <SwapDropdown
                    options={originKindOptions}
                    value={origin.value}
                    onChange={this.onChangeOriginKind}
                  />
                </label>
                {originErr && <span className="CurrencySwap-error-message">{originErr}</span>}
              </div>

              <div className="input-group-wrapper">
                <label className="input-group input-group-inline-dropdown">
                  <div className="input-group-header">Recieve</div>
                  <Input
                    id="destination-swap-input"
                    className={`${
                      String(destination.amount) !== '' &&
                      this.isMinMaxValid(origin.amount, origin.label, destination.label)
                        ? ''
                        : 'invalid'
                    }`}
                    type="number"
                    placeholder="Amount"
                    value={isNaN(destination.amount) ? '' : destination.amount}
                    onChange={this.onChangeAmount}
                  />
                  <SwapDropdown
                    options={destinationKindOptions}
                    value={destination.value}
                    onChange={this.onChangeDestinationKind}
                  />
                </label>
                {destinationErr && (
                  <span className="CurrencySwap-error-message">{destinationErr}</span>
                )}
              </div>
              <div className="flex-spacer" />
            </div>
            <div className="CurrencySwap-submit">
              <SimpleButton
                onClick={this.onClickStartSwap}
                text={translate('SWAP_init_CTA')}
                disabled={this.state.disabled}
                type="primary"
              />
            </div>
          </React.Fragment>
        ) : (
          <Spinner />
        )}
      </article>
    );
  }
}
