// This class handles the Account Lockout Policy settings.
import Parse from 'parse/node';

export class AccountLockout {
  constructor(user, config) {
    this._user = user;
    this._config = config;
  }

  /**
   * set _failed_login_count to value
   */
  _setFailedLoginCount(value) {
    const query = {
      username: this._user.username,
    };

    const updateFields = {
      _failed_login_count: value,
    };

    return this._config.database.update('_User', query, updateFields);
  }

  /**
   * check if the _failed_login_count field has been set
   */
  _isFailedLoginCountSet() {
    const query = {
      username: this._user.username,
      _failed_login_count: { $exists: true },
    };

    return this._config.database.find('_User', query).then(users => {
      if (Array.isArray(users) && users.length > 0) {
        return true;
      } else {
        return false;
      }
    });
  }

  /**
   * if _failed_login_count is NOT set then set it to 0
   * else do nothing
   */
  _initFailedLoginCount() {
    return this._isFailedLoginCountSet().then(failedLoginCountIsSet => {
      if (!failedLoginCountIsSet) {
        return this._setFailedLoginCount(0);
      }
    });
  }

  /**
   * increment _failed_login_count by 1
   */
  _incrementFailedLoginCount() {
    const query = {
      username: this._user.username,
    };

    const updateFields = {
      _failed_login_count: { __op: 'Increment', amount: 1 },
    };

    return this._config.database.update('_User', query, updateFields);
  }

  /**
   * if the failed login count is greater than the threshold
   * then sets lockout expiration to 'currenttime + accountPolicy.duration', i.e., account is locked out for the next 'accountPolicy.duration' minutes
   * else do nothing
   */
  _setLockoutExpiration() {
    const query = {
      username: this._user.username,
      _failed_login_count: { $gte: this._config.accountLockout.threshold },
    };

    const now = new Date();

    const updateFields = {
      _account_lockout_expires_at: Parse._encode(
        new Date(now.getTime() + this._config.accountLockout.duration * 60 * 1000)
      ),
    };

    return this._config.database.update('_User', query, updateFields).catch(err => {
      if (
        err &&
        err.code &&
        err.message &&
        err.code === Parse.Error.OBJECT_NOT_FOUND &&
        err.message === 'Object not found.'
      ) {
        return; // nothing to update so we are good
      } else {
        throw err; // unknown error
      }
    });
  }

  /**
   * if _account_lockout_expires_at > current_time and _failed_login_count > threshold
   *   reject with account locked error
   * else
   *   resolve
   */
  _notLocked() {
    const query = {
      username: this._user.username,
      _account_lockout_expires_at: { $gt: Parse._encode(new Date()) },
      _failed_login_count: { $gte: this._config.accountLockout.threshold },
    };

    return this._config.database.find('_User', query).then(users => {
      if (Array.isArray(users) && users.length > 0) {
        throw new Parse.Error(
          Parse.Error.OBJECT_NOT_FOUND,
          'Your account is locked due to multiple failed login attempts. Please try again after ' +
            this._config.accountLockout.duration +
            ' minute(s)'
        );
      }
    });
  }

  /**
   * set and/or increment _failed_login_count
   * if _failed_login_count > threshold
   *   set the _account_lockout_expires_at to current_time + accountPolicy.duration
   * else
   *   do nothing
   */
  _handleFailedLoginAttempt() {
    return this._initFailedLoginCount()
      .then(() => {
        return this._incrementFailedLoginCount();
      })
      .then(() => {
        return this._setLockoutExpiration();
      });
  }

  /**
   * handle login attempt if the Account Lockout Policy is enabled
   */
  handleLoginAttempt(loginSuccessful) {
    if (!this._config.accountLockout) {
      return Promise.resolve();
    }
    return this._notLocked().then(() => {
      if (loginSuccessful) {
        return this._setFailedLoginCount(0);
      } else {
        return this._handleFailedLoginAttempt();
      }
    });
  }

  /**
   * Removes the account lockout.
   */
  unlockAccount() {
    if (!this._config.accountLockout || !this._config.accountLockout.unlockOnPasswordReset) {
      return Promise.resolve();
    }
    return this._config.database.update(
      '_User',
      { username: this._user.username },
      {
        _failed_login_count: { __op: 'Delete' },
        _account_lockout_expires_at: { __op: 'Delete' },
      }
    );
  }
}

export default AccountLockout;
