import React from 'react';
import classNames from 'classnames';

import { SessionInput } from './SessionInput';
import {
  SessionButton,
  SessionButtonColor,
  SessionButtonType,
} from './SessionButton';
import { trigger } from '../../shims/events';
import { SessionHtmlRenderer } from './SessionHTMLRenderer';
import { SessionIdEditable } from './SessionIdEditable';
import { SessionSpinner } from './SessionSpinner';

enum SignInMode {
  Default,
  UsingSeed,
  LinkingDevice,
}

enum SignUpMode {
  Default,
  SessionIDShown,
  EnterDetails,
}

enum TabType {
  Create,
  SignIn,
}

interface State {
  selectedTab: TabType;
  signInMode: SignInMode;
  signUpMode: SignUpMode;
  displayName: string;
  password: string;
  validatePassword: string;
  passwordErrorString: string;
  passwordFieldsMatch: boolean;
  mnemonicSeed: string;
  hexGeneratedPubKey: string;
  primaryDevicePubKey: string;
  mnemonicError: string | undefined;
  displayNameError: string | undefined;
  loading: boolean;
}

const Tab = ({
  isSelected,
  label,
  onSelect,
  type,
}: {
  isSelected: boolean;
  label: string;
  onSelect?: (event: TabType) => void;
  type: TabType;
}) => {
  const handleClick = onSelect
    ? () => {
        onSelect(type);
      }
    : undefined;

  return (
    <div
      className={classNames(
        'session-registration__tab',
        isSelected ? 'session-registration__tab--active' : null
      )}
      onClick={handleClick}
      role="tab"
    >
      {label}
    </div>
  );
};

export class RegistrationTabs extends React.Component<{}, State> {
  private readonly accountManager: any;

  constructor(props: any) {
    super(props);

    this.onSeedChanged = this.onSeedChanged.bind(this);
    this.onDisplayNameChanged = this.onDisplayNameChanged.bind(this);
    this.onPasswordChanged = this.onPasswordChanged.bind(this);
    this.onPasswordVerifyChanged = this.onPasswordVerifyChanged.bind(this);
    this.onSignUpGenerateSessionIDClick = this.onSignUpGenerateSessionIDClick.bind(
      this
    );
    this.onSignUpGetStartedClick = this.onSignUpGetStartedClick.bind(this);
    this.onSecondDeviceSessionIDChanged = this.onSecondDeviceSessionIDChanged.bind(
      this
    );
    this.onSecondaryDeviceRegistered = this.onSecondaryDeviceRegistered.bind(
      this
    );
    this.onCompleteSignUpClick = this.onCompleteSignUpClick.bind(this);
    this.handlePressEnter = this.handlePressEnter.bind(this);
    this.handleContinueYourSessionClick = this.handleContinueYourSessionClick.bind(
      this
    );

    this.state = {
      selectedTab: TabType.Create,
      signInMode: SignInMode.Default,
      signUpMode: SignUpMode.Default,
      displayName: '',
      password: '',
      validatePassword: '',
      passwordErrorString: '',
      passwordFieldsMatch: false,
      mnemonicSeed: '',
      hexGeneratedPubKey: '',
      primaryDevicePubKey: '',
      mnemonicError: undefined,
      displayNameError: undefined,
      loading: false,
    };

    this.accountManager = window.getAccountManager();
    // Clean status in case the app closed unexpectedly
    window.textsecure.storage.remove('secondaryDeviceStatus');
  }

  public render() {
    this.generateMnemonicAndKeyPair().ignore();

    return this.renderTabs();
  }

  private async generateMnemonicAndKeyPair() {
    if (this.state.mnemonicSeed === '') {
      const language = 'english';
      const mnemonic = await this.accountManager.generateMnemonic(language);

      let seedHex = window.mnemonic.mn_decode(mnemonic, language);
      // handle shorter than 32 bytes seeds
      const privKeyHexLength = 32 * 2;
      if (seedHex.length !== privKeyHexLength) {
        seedHex = seedHex.concat(seedHex);
        seedHex = seedHex.substring(0, privKeyHexLength);
      }
      const seed = window.dcodeIO.ByteBuffer.wrap(
        seedHex,
        'hex'
      ).toArrayBuffer();
      const keyPair = await window.libsignal.Curve.async.createKeyPair(seed);
      const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex');

      this.setState({
        mnemonicSeed: mnemonic,
        hexGeneratedPubKey, // our 'frontend' sessionID
      });
    }
  }

  private renderTabs() {
    const { selectedTab } = this.state;

    const createAccount = window.i18n('createAccount');
    const signIn = window.i18n('signIn');
    const isCreateSelected = selectedTab === TabType.Create;
    const isSignInSelected = selectedTab === TabType.SignIn;

    return (
      <div className="session-registration-container">
        <div className="session-registration__tab-container">
          <Tab
            label={createAccount}
            type={TabType.Create}
            isSelected={isCreateSelected}
            onSelect={this.handleTabSelect}
          />
          <Tab
            label={signIn}
            type={TabType.SignIn}
            isSelected={isSignInSelected}
            onSelect={this.handleTabSelect}
          />
        </div>
        {this.renderSections()}
      </div>
    );
  }

  private readonly handleTabSelect = (tabType: TabType): void => {
    if (tabType !== TabType.SignIn) {
      this.cancelSecondaryDevice().ignore();
    }
    this.setState({
      selectedTab: tabType,
      signInMode: SignInMode.Default,
      signUpMode: SignUpMode.Default,
      displayName: '',
      password: '',
      validatePassword: '',
      passwordErrorString: '',
      passwordFieldsMatch: false,
      mnemonicSeed: '',
      hexGeneratedPubKey: '',
      primaryDevicePubKey: '',
      mnemonicError: undefined,
      displayNameError: undefined,
    });
  };

  private onSeedChanged(val: string) {
    this.setState({
      mnemonicSeed: val,
      mnemonicError: !val ? window.i18n('mnemonicEmpty') : undefined,
    });
  }

  private onDisplayNameChanged(val: string) {
    const sanitizedName = this.sanitiseNameInput(val);
    const trimName = sanitizedName.trim();

    this.setState({
      displayName: sanitizedName,
      displayNameError: !trimName ? window.i18n('displayNameEmpty') : undefined,
    });
  }

  private onPasswordChanged(val: string) {
    this.setState({ password: val }, () => {
      this.validatePassword();
    });
  }

  private onPasswordVerifyChanged(val: string) {
    this.setState({ validatePassword: val });
    this.setState({ validatePassword: val }, () => {
      this.validatePassword();
    });
  }

  private renderSections() {
    const { selectedTab } = this.state;
    if (selectedTab === TabType.Create) {
      return this.renderSignUp();
    }

    return this.renderSignIn();
  }

  private renderSignUp() {
    const { signUpMode } = this.state;
    switch (signUpMode) {
      case SignUpMode.Default:
        return (
          <div className="session-registration__content">
            {this.renderSignUpHeader()}
            {this.renderSignUpButton()}
          </div>
        );
      case SignUpMode.SessionIDShown:
        return (
          <div className="session-registration__content">
            {this.renderSignUpHeader()}
            <div className="session-registration__unique-session-id">
              {window.i18n('yourUniqueSessionID')}
            </div>
            {this.renderEnterSessionID(false)}
            {this.renderSignUpButton()}
            {this.getRenderTermsConditionAgreement()}
          </div>
        );

      default:
        const {
          passwordErrorString,
          passwordFieldsMatch,
          displayNameError,
          displayName,
          password,
        } = this.state;

        let enableCompleteSignUp = true;
        const displayNameOK = !displayNameError && !!displayName; //display name required
        const passwordsOK =
          !password || (!passwordErrorString && passwordFieldsMatch); // password is valid if empty, or if no error and fields are matching

        enableCompleteSignUp = displayNameOK && passwordsOK;

        return (
          <div className="session-registration__content">
            <div className="session-registration__welcome-session">
              {window.i18n('welcomeToYourSession')}
            </div>

            {this.renderRegistrationContent()}
            <SessionButton
              onClick={() => {
                this.onCompleteSignUpClick();
              }}
              buttonType={SessionButtonType.Brand}
              buttonColor={SessionButtonColor.Green}
              text={window.i18n('getStarted')}
              disabled={!enableCompleteSignUp}
            />
          </div>
        );
    }
  }

  private getRenderTermsConditionAgreement() {
    const { selectedTab, signInMode, signUpMode } = this.state;
    if (selectedTab === TabType.Create) {
      return signUpMode !== SignUpMode.Default
        ? this.renderTermsConditionAgreement()
        : null;
    } else {
      return signInMode !== SignInMode.Default
        ? this.renderTermsConditionAgreement()
        : null;
    }
  }

  private renderSignUpHeader() {
    const allUsersAreRandomly = window.i18n('allUsersAreRandomly...');

    return (
      <div className="session-description-long">{allUsersAreRandomly}</div>
    );
  }

  private renderSignUpButton() {
    const { signUpMode } = this.state;

    let buttonType: SessionButtonType;
    let buttonColor: SessionButtonColor;
    let buttonText: string;
    if (signUpMode !== SignUpMode.Default) {
      buttonType = SessionButtonType.Brand;
      buttonColor = SessionButtonColor.Green;
      buttonText = window.i18n('continue');
    } else {
      buttonType = SessionButtonType.BrandOutline;
      buttonColor = SessionButtonColor.Green;
      buttonText = window.i18n('generateSessionID');
    }

    return (
      <SessionButton
        onClick={() => {
          if (signUpMode === SignUpMode.Default) {
            this.onSignUpGenerateSessionIDClick().ignore();
          } else {
            this.onSignUpGetStartedClick();
          }
        }}
        buttonType={buttonType}
        buttonColor={buttonColor}
        text={buttonText}
      />
    );
  }

  private async onSignUpGenerateSessionIDClick() {
    this.setState(
      {
        signUpMode: SignUpMode.SessionIDShown,
      },
      () => {
        window.Session.setNewSessionID(this.state.hexGeneratedPubKey);
      }
    );
  }

  private onSignUpGetStartedClick() {
    this.setState({
      signUpMode: SignUpMode.EnterDetails,
    });
  }

  private onCompleteSignUpClick() {
    this.register('english').ignore();
  }

  private renderSignIn() {
    return (
      <div className="session-registration__content">
        {this.renderRegistrationContent()}

        {this.renderSignInButtons()}
        {this.getRenderTermsConditionAgreement()}
      </div>
    );
  }

  private renderRegistrationContent() {
    const { signInMode, signUpMode } = this.state;

    if (signInMode === SignInMode.UsingSeed) {
      return (
        <div className={classNames('session-registration__entry-fields')}>
          <SessionInput
            label={window.i18n('mnemonicSeed')}
            type="password"
            placeholder={window.i18n('enterSeed')}
            enableShowHide={true}
            onValueChanged={(val: string) => {
              this.onSeedChanged(val);
            }}
            onEnterPressed={() => {
              this.handlePressEnter();
            }}
          />
          {this.renderNamePasswordAndVerifyPasswordFields()}
        </div>
      );
    }
    if (signInMode === SignInMode.LinkingDevice) {
      return (
        <div className="registration-content-centered">
          <div className="session-signin-device-pairing-header">
            {window.i18n('devicePairingHeader')}
          </div>
          {this.renderEnterSessionID(true)}
          <SessionSpinner loading={this.state.loading} />
        </div>
      );
    }
    if (signUpMode === SignUpMode.EnterDetails) {
      return (
        <div className={classNames('session-registration__entry-fields')}>
          {this.renderNamePasswordAndVerifyPasswordFields()}
        </div>
      );
    }

    return null;
  }

  private renderNamePasswordAndVerifyPasswordFields() {
    const { password, passwordFieldsMatch } = this.state;
    const passwordsDoNotMatch =
      !passwordFieldsMatch && this.state.password
        ? window.i18n('passwordsDoNotMatch')
        : undefined;

    return (
      <div className="inputfields">
        <SessionInput
          label={window.i18n('displayName')}
          type="text"
          placeholder={window.i18n('enterDisplayName')}
          value={this.state.displayName}
          maxLength={window.CONSTANTS.MAX_USERNAME_LENGTH}
          onValueChanged={(val: string) => {
            this.onDisplayNameChanged(val);
          }}
          onEnterPressed={() => {
            this.handlePressEnter();
          }}
        />

        <SessionInput
          label={window.i18n('optionalPassword')}
          error={this.state.passwordErrorString}
          type="password"
          placeholder={window.i18n('enterOptionalPassword')}
          maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
          onValueChanged={(val: string) => {
            this.onPasswordChanged(val);
          }}
          onEnterPressed={() => {
            this.handlePressEnter();
          }}
        />

        {!!password && (
          <SessionInput
            label={window.i18n('verifyPassword')}
            error={passwordsDoNotMatch}
            type="password"
            placeholder={window.i18n('verifyPassword')}
            maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
            onValueChanged={(val: string) => {
              this.onPasswordVerifyChanged(val);
            }}
            onEnterPressed={() => {
              this.handlePressEnter();
            }}
          />
        )}
      </div>
    );
  }

  private renderEnterSessionID(contentEditable: boolean) {
    const enterSessionIDHere = window.i18n('enterSessionIDHere');

    return (
      <SessionIdEditable
        editable={contentEditable}
        placeholder={enterSessionIDHere}
        onChange={(value: string) => {
          this.onSecondDeviceSessionIDChanged(value);
        }}
      />
    );
  }

  private onSecondDeviceSessionIDChanged(value: string) {
    this.setState({
      primaryDevicePubKey: value,
    });
  }

  private renderSignInButtons() {
    const { signInMode } = this.state;

    const or = window.i18n('or');

    if (signInMode === SignInMode.Default) {
      return (
        <div>
          {this.renderRestoreUsingSeedButton(
            SessionButtonType.BrandOutline,
            SessionButtonColor.Green
          )}
          <h4>{or}</h4>
          {this.renderLinkDeviceToExistingAccountButton()}
        </div>
      );
    }

    if (signInMode === SignInMode.LinkingDevice) {
      return (
        <div>
          {this.renderContinueYourSessionButton()}
          <h4>{or}</h4>
          {this.renderRestoreUsingSeedButton(
            SessionButtonType.BrandOutline,
            SessionButtonColor.White
          )}
        </div>
      );
    }

    return (
      <div>
        {this.renderContinueYourSessionButton()}
        <h4>{or}</h4>
        {this.renderLinkDeviceToExistingAccountButton()}
      </div>
    );
  }

  private renderTermsConditionAgreement() {
    // FIXME add link to our Terms and Conditions and privacy statement

    return (
      <div className="session-terms-conditions-agreement">
        <SessionHtmlRenderer html={window.i18n('ByUsingThisService...')} />
      </div>
    );
  }

  private handleContinueYourSessionClick() {
    if (this.state.signInMode === SignInMode.UsingSeed) {
      this.register('english').ignore();
    } else {
      this.registerSecondaryDevice().ignore();
    }
  }

  private renderContinueYourSessionButton() {
    const {
      signUpMode,
      signInMode,
      passwordErrorString,
      passwordFieldsMatch,
      displayNameError,
      mnemonicError,
      primaryDevicePubKey,
      displayName,
      mnemonicSeed,
      password,
    } = this.state;

    let enableContinue = true;
    let text = window.i18n('continueYourSession');
    const displayNameOK = !displayNameError && !!displayName; //display name required
    const mnemonicOK = !mnemonicError && !!mnemonicSeed; //Mnemonic required
    const passwordsOK =
      !password || (!passwordErrorString && passwordFieldsMatch); // password is valid if empty, or if no error and fields are matching
    if (signInMode === SignInMode.UsingSeed) {
      enableContinue = displayNameOK && mnemonicOK && passwordsOK;
    } else if (signInMode === SignInMode.LinkingDevice) {
      enableContinue = !!primaryDevicePubKey;
      text = window.i18n('linkDevice');
    } else if (signUpMode === SignUpMode.EnterDetails) {
      enableContinue = displayNameOK && passwordsOK;
    }

    return (
      <SessionButton
        onClick={() => {
          this.handleContinueYourSessionClick();
        }}
        buttonType={SessionButtonType.Brand}
        buttonColor={SessionButtonColor.Green}
        text={text}
        disabled={!enableContinue}
      />
    );
  }

  private renderRestoreUsingSeedButton(
    buttonType: SessionButtonType,
    buttonColor: SessionButtonColor
  ) {
    return (
      <SessionButton
        onClick={() => {
          this.cancelSecondaryDevice().ignore();
          this.setState({
            signInMode: SignInMode.UsingSeed,
            primaryDevicePubKey: '',
            mnemonicSeed: '',
            displayName: '',
            signUpMode: SignUpMode.Default,
          });
        }}
        buttonType={buttonType}
        buttonColor={buttonColor}
        text={window.i18n('restoreUsingSeed')}
      />
    );
  }

  private renderLinkDeviceToExistingAccountButton() {
    return (
      <SessionButton
        onClick={() => {
          this.setState({
            signInMode: SignInMode.LinkingDevice,
            mnemonicSeed: '',
            displayName: '',
            signUpMode: SignUpMode.Default,
          });
        }}
        buttonType={SessionButtonType.BrandOutline}
        buttonColor={SessionButtonColor.White}
        text={window.i18n('linkDeviceToExistingAccount')}
      />
    );
  }

  private handlePressEnter() {
    const { signInMode, signUpMode } = this.state;
    if (signUpMode === SignUpMode.EnterDetails) {
      this.onCompleteSignUpClick();

      return;
    }

    if (signInMode === SignInMode.UsingSeed) {
      this.handleContinueYourSessionClick();

      return;
    }
  }

  private trim(value: string) {
    return value ? value.trim() : value;
  }

  private validatePassword() {
    const input = this.trim(this.state.password);
    const confirmationInput = this.trim(this.state.validatePassword);

    // If user hasn't set a value then skip
    if (!input && !confirmationInput) {
      this.setState({
        passwordErrorString: '',
        passwordFieldsMatch: true,
      });

      return;
    }

    const error = window.passwordUtil.validatePassword(input, window.i18n);
    if (error) {
      this.setState({
        passwordErrorString: error,
        passwordFieldsMatch: true,
      });

      return;
    }

    if (input !== confirmationInput) {
      this.setState({
        passwordErrorString: '',
        passwordFieldsMatch: false,
      });

      return;
    }

    this.setState({
      passwordErrorString: '',
      passwordFieldsMatch: true,
    });
  }

  private sanitiseNameInput(val: string) {
    return val.replace(window.displayNameRegex, '');
  }

  private async resetRegistration() {
    await window.Signal.Data.removeAllIdentityKeys();
    await window.Signal.Data.removeAllPrivateConversations();
    window.Whisper.Registration.remove();
    // Do not remove all items since they are only set
    // at startup.
    window.textsecure.storage.remove('identityKey');
    window.textsecure.storage.remove('secondaryDeviceStatus');
    window.ConversationController.reset();
    await window.ConversationController.load();
    window.Whisper.RotateSignedPreKeyListener.stop(window.Whisper.events);
  }

  private async register(language: string) {
    const {
      password,
      mnemonicSeed,
      displayName,
      passwordErrorString,
      passwordFieldsMatch,
    } = this.state;
    // Make sure the password is valid

    const trimName = displayName.trim();

    if (!trimName) {
      window.pushToast({
        title: window.i18n('displayNameEmpty'),
        type: 'error',
        id: 'invalidDisplayName',
      });

      return;
    }

    if (passwordErrorString) {
      window.pushToast({
        title: window.i18n('invalidPassword'),
        type: 'error',
        id: 'invalidPassword',
      });

      return;
    }

    if (!!password && !passwordFieldsMatch) {
      window.pushToast({
        title: window.i18n('passwordsDoNotMatch'),
        type: 'error',
        id: 'invalidPassword',
      });

      return;
    }

    if (!mnemonicSeed) {
      return;
    }

    // Ensure we clear the secondary device registration status
    window.textsecure.storage.remove('secondaryDeviceStatus');

    try {
      await this.resetRegistration();

      await window.setPassword(password);
      await this.accountManager.registerSingleDevice(
        mnemonicSeed,
        language,
        trimName
      );
      trigger('openInbox');
    } catch (e) {
      if (typeof e === 'string') {
        //this.showToast(e);
      }
      //this.log(e);
    }
  }

  private async cancelSecondaryDevice() {
    window.Whisper.events.off(
      'secondaryDeviceRegistration',
      this.onSecondaryDeviceRegistered
    );

    await this.resetRegistration();
  }

  private async registerSecondaryDevice() {
    // tslint:disable-next-line: no-backbone-get-set-outside-model
    if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') {
      return;
    }
    this.setState({
      loading: true,
    });
    await this.resetRegistration();
    window.textsecure.storage.put('secondaryDeviceStatus', 'ongoing');

    const primaryPubKey = this.state.primaryDevicePubKey;

    // Ensure only one listener
    window.Whisper.events.off(
      'secondaryDeviceRegistration',
      this.onSecondaryDeviceRegistered
    );
    window.Whisper.events.once(
      'secondaryDeviceRegistration',
      this.onSecondaryDeviceRegistered
    );

    const onError = async (error: any) => {
      window.log.error(error);
      // clear the ... to make sure the user realize we're not doing anything
      this.setState({
        loading: false,
      });
      await this.resetRegistration();
    };

    const c = new window.Whisper.Conversation({
      id: primaryPubKey,
      type: 'private',
    });

    const validationError = c.validateNumber();
    if (validationError) {
      onError('Invalid public key').ignore();
      window.pushToast({
        title: window.i18n('invalidNumberError'),
        type: 'error',
        id: 'invalidNumberError',
      });

      return;
    }
    try {
      const fakeMnemonic = this.state.mnemonicSeed;

      await this.accountManager.registerSingleDevice(
        fakeMnemonic,
        'english',
        null
      );

      await this.accountManager.requestPairing(primaryPubKey);
      const pubkey = window.textsecure.storage.user.getNumber();
      const words = window.mnemonic.pubkey_to_secret_words(pubkey);
      window.console.log(`Here is your secret:\n${words}`);
      window.pushToast({
        title: `${window.i18n('secretPrompt')}`,
        description: words,
        id: 'yourSecret',
        shouldFade: false,
      });
    } catch (e) {
      window.console.log(e);
      this.setState({
        loading: false,
      });
    }
  }

  private async onSecondaryDeviceRegistered() {
    // Ensure the left menu is updated
    this.setState({
      loading: false,
    });
    trigger('userChanged', { isSecondaryDevice: true });
    // will re-run the background initialisation
    trigger('registration_done');
    trigger('openInbox');
  }
}
