import * as plugins from '../plugins.js';

import { RegistrationSessionManager } from './classes.registrationsessionmanager.js';
import { logger } from './logging.js';
import { User } from './classes.user.js';

/**
 * a RegistrationSession is a in memory session for signing up
 */
export class RegistrationSession {
  // ======
  // STATIC
  // ======
  public static async createRegistrationSessionForEmail(
    registrationSessionManageremailArg: RegistrationSessionManager,
    emailArg: string
  ) {
    const newRegistrationSession = new RegistrationSession(
      registrationSessionManageremailArg,
      emailArg
    );
    const emailValidationResult = await newRegistrationSession
      .validateEMailAddress()
      .catch((error) => {
        throw new plugins.typedrequest.TypedResponseError(
          'Error occured during email provider & dns validation'
        );
      });
    if (!emailValidationResult?.valid) {
      newRegistrationSession.destroy();
      throw new plugins.typedrequest.TypedResponseError(
        'Email Address is not valid. Please use a correctly formated email address'
      );
    }
    if (emailValidationResult.disposable) {
      newRegistrationSession.destroy();
      throw new plugins.typedrequest.TypedResponseError(
        'Email is disposable. Please use a non disposable email address.'
      );
    }
    console.log(
      `${newRegistrationSession.emailAddress} is valid. Continuing registration process!`
    );
    await newRegistrationSession.sendTokenValidationEmail();
    console.log(`Successfully sent email validation email`);
    return newRegistrationSession;
  }

  // ========
  // INSTANCE
  // ========
  public registrationSessionManagerRef: RegistrationSessionManager;

  public emailAddress: string;

  /**
   * only used during testing
   */
  public unhashedEmailToken?: string;
  public hashedEmailToken: string;
  private smsvalidationCounter = 0;
  public smsCode: string;

  /**
   * the status of the registration. should progress in a linear fashion.
   */
  public status: 'announced' | 'emailValidated' | 'mobileVerified' | 'registered' | 'failed' =
    'announced';

  public collectedData: {
    userData: plugins.idpInterfaces.data.IUser['data'];
  } = {
    userData: {
      username: null,
      connectedOrgs: [],
      email: null,
      name: null,
      status: null,
      mobileNumber: null,
      password: null,
      passwordHash: null,
    },
  };

  constructor(
    registrationSessionManagerRefArg: RegistrationSessionManager,
    emailAddressArg: string
  ) {
    this.registrationSessionManagerRef = registrationSessionManagerRefArg;
    this.emailAddress = emailAddressArg;
    this.registrationSessionManagerRef.registrationSessions.addToMap(this.emailAddress, this);

    // lets destroy this after 10 minutes,
    // works in unrefed mode so not blocking node exiting.
    plugins.smartdelay.delayFor(600000, null, true).then(() => this.destroy());
  }

  /**
   * validates a token by comparing its hash against the stored hashed token
   * @param tokenArg
   */
  public validateEmailToken(tokenArg: string): boolean {
    const result = this.hashedEmailToken === plugins.smarthash.sha256FromStringSync(tokenArg);
    if (result && this.status === 'announced') {
      this.status = 'emailValidated';
      this.collectedData.userData.email = this.emailAddress;
    }
    if (!result && this.status === 'announced') {
      this.status = 'failed';
    }
    return result;
  }

  /** validates the sms code */
  public validateSmsCode(smsCodeArg: string) {
    this.smsvalidationCounter++;
    const result = this.smsCode === smsCodeArg;
    if (this.status === 'emailValidated' && result) {
      this.status = 'mobileVerified';
      return result;
    } else {
      if (this.smsvalidationCounter === 5) {
        this.destroy();
        throw new plugins.typedrequest.TypedResponseError(
          'Registration cancelled due to repeated wrong verification code submission'
        );
      }
      return false;
    }
  }

  /**
   * validate the email address with provider and dns sanity checks
   * @returns
   */
  public async validateEMailAddress(): Promise<plugins.smartmail.IEmailValidationResult> {
    console.log(`validating email ${this.emailAddress}`);
    const result = await new plugins.smartmail.EmailAddressValidator().validate(this.emailAddress);
    return result;
  }

  /**
   * send the validation email
   */
  public async sendTokenValidationEmail() {
    const uuidToSend = plugins.smartunique.uuid4();
    this.unhashedEmailToken = uuidToSend;
    this.hashedEmailToken = plugins.smarthash.sha256FromStringSync(uuidToSend);
    this.registrationSessionManagerRef.receptionRef.receptionMailer.sendRegistrationEmail(
      this,
      uuidToSend
    );
    logger.log('info', `sent a validation email with a verification code to ${this.emailAddress}`);
  }

  /**
   * validate the mobile number of someone
   */
  public async sendValidationSms() {
    this.smsCode =
      await this.registrationSessionManagerRef.receptionRef.szPlatformClient.smsConnector.sendSmsVerifcation(
        {
          fromName: this.registrationSessionManagerRef.receptionRef.options.name,
          toNumber: parseInt(this.collectedData.userData.mobileNumber),
        }
      );
  }

  /**
   * this method can be called when this registrationsession is validated
   * and all data has been set
   */
  public async manifestUserWithAccountData(): Promise<User> {
    if (this.status !== 'mobileVerified') {
      throw new plugins.typedrequest.TypedResponseError(
        'You can only manifest user that have a validated email Address and Mobile Number'
      );
    }
    if (!this.collectedData) {
      throw new Error('You have to set the accountdata first');
    }
    const manifestedUser =
      await this.registrationSessionManagerRef.receptionRef.userManager.CUser.createNewUserForUserData(
        this.collectedData.userData
      );
    return manifestedUser;
  }

  /**
   * destroys the registrationsession
   */
  public destroy() {
    this.registrationSessionManagerRef.registrationSessions.removeFromMap(this.emailAddress);
  }
}
