/**
   * This method checks if the user account exist or is locked. If the account is locked, the
   * authentication process will be terminated after this method returning false.
   */
  @Override
  public boolean doPreAuthenticate(
      String userName, Object credential, UserStoreManager userStoreManager)
      throws UserStoreException {

    if (log.isDebugEnabled()) {
      log.debug("Pre authenticator is called in IdentityMgtEventListener");
    }

    IdentityMgtConfig config = IdentityMgtConfig.getInstance();

    if (!config.isEnableAuthPolicy()) {
      return true;
    }

    if (config.isAuthPolicyAccountExistCheck() && !userStoreManager.isExistingUser(userName)) {
      log.warn("User name does not exist in system : " + userName);
      throw new UserStoreException(UserCoreConstants.ErrorCode.USER_DOES_NOT_EXIST);
    }

    UserIdentityClaimsDO userIdentityDTO = module.load(userName, userStoreManager);

    // if the account is locked, should not be able to log in
    if (userIdentityDTO != null && userIdentityDTO.isAccountLocked()) {

      // If unlock time is specified then unlock the account.
      if ((userIdentityDTO.getUnlockTime() != 0)
          && (System.currentTimeMillis() >= userIdentityDTO.getUnlockTime())) {

        userIdentityDTO.setAccountLock(false);
        userIdentityDTO.setUnlockTime(0);

        try {
          module.store(userIdentityDTO, userStoreManager);
        } catch (IdentityException e) {
          throw new UserStoreException("Error while saving user", e);
        }
      } else {

        log.warn(
            "User account is locked for user : "******". cannot login until the account is unlocked ");
        throw new UserStoreException(UserCoreConstants.ErrorCode.USER_IS_LOCKED);
      }
    }

    return true;
  }
  public UserIdentityClaimsDO(String userName, Map<String, String> userDataMap) {

    this.userName = userName;
    this.userIdentityDataMap = userDataMap;

    if (userDataMap.get(UserIdentityDataStore.FAIL_LOGIN_ATTEMPTS) != null) {
      setFailAttempts(Integer.parseInt(userDataMap.get(UserIdentityDataStore.FAIL_LOGIN_ATTEMPTS)));
    }
    if (userDataMap.get(UserIdentityDataStore.LAST_FAILED_LOGIN_ATTEMPT_TIME) != null) {
      setLastFailAttemptTime(
          Long.parseLong(userDataMap.get(UserIdentityDataStore.LAST_FAILED_LOGIN_ATTEMPT_TIME)));
    }
    if (userDataMap.get(UserIdentityDataStore.UNLOCKING_TIME) != null) {
      setUnlockTime(Long.parseLong(userDataMap.get(UserIdentityDataStore.UNLOCKING_TIME)));
    }
    if (userDataMap.get(UserIdentityDataStore.ONE_TIME_PASSWORD) != null) {
      setOneTimeLogin(
          Boolean.parseBoolean(userDataMap.get(UserIdentityDataStore.ONE_TIME_PASSWORD)));
    }
    if (userDataMap.get(UserIdentityDataStore.PASSWORD_CHANGE_REQUIRED) != null) {
      setPasswordChangeRequired(
          Boolean.parseBoolean(userDataMap.get(UserIdentityDataStore.PASSWORD_CHANGE_REQUIRED)));
    }
    if (userDataMap.get(UserIdentityDataStore.LAST_LOGON_TIME) != null) {
      setLastLogonTime(Long.parseLong(userDataMap.get(UserIdentityDataStore.LAST_LOGON_TIME)));
    }
    if (userDataMap.get(UserIdentityDataStore.ACCOUNT_LOCK) != null) {
      setAccountLock(Boolean.parseBoolean(userDataMap.get(UserIdentityDataStore.ACCOUNT_LOCK)));
    }
    if (userDataMap.get(UserIdentityDataStore.PASSWORD_TIME_STAMP) != null) {
      setPasswordTimeStamp(
          Long.parseLong(userDataMap.get(UserIdentityDataStore.PASSWORD_TIME_STAMP)));
    }
    //		if (userDataMap.get(UserIdentityDataStore.PASSWORD_TIME_STAMP) != null) {
    //
    //	setPasswordTimeStamp(Long.parseLong(userDataMap.get(UserIdentityDataStore.PASSWORD_TIME_STAMP)));
    //		}
  }
  /**
   * This method locks the created accounts based on the account policies or based on the account
   * confirmation method being used. Two account confirmation methods are used : Temporary Password
   * and Verification Code. In the case of temporary password is used the temporary password will be
   * emailed to the user. In the case of verification code, the code will be emailed to the user.
   * The security questions filter ad doPreAddUser will be persisted in this method.
   */
  @Override
  public boolean doPostAddUser(
      String userName,
      Object credential,
      String[] roleList,
      Map<String, String> claims,
      String profile,
      UserStoreManager userStoreManager)
      throws UserStoreException {
    if (log.isDebugEnabled()) {
      log.debug("Post add user is called in IdentityMgtEventListener");
    }
    IdentityMgtConfig config = IdentityMgtConfig.getInstance();
    if (!config.isListenerEnable()) {
      return true;
    }
    // reading the value from the thread local
    UserIdentityClaimsDO userIdentityClaimsDO =
        (UserIdentityClaimsDO) threadLocalProperties.get().get(USER_IDENTITY_DO);

    if (config.isEnableUserAccountVerification()) {

      // empty password account creation
      if (threadLocalProperties.get().containsKey(EMPTY_PASSWORD_USED)) {
        // store identity data
        userIdentityClaimsDO.setAccountLock(false).setPasswordTimeStamp(System.currentTimeMillis());
        try {
          module.store(userIdentityClaimsDO, userStoreManager);
        } catch (IdentityException e) {
          throw new UserStoreException("Error while doPostAddUser", e);
        }
        // store identity metadata
        UserRecoveryDataDO metadataDO = new UserRecoveryDataDO();
        metadataDO
            .setUserName(userName)
            .setTenantId(userStoreManager.getTenantId())
            .setCode((String) credential);
        //				try {
        //	                UserIdentityManagementUtil.storeUserIdentityMetadata(metadataDO);
        //                } catch (IdentityException e) {
        //                	throw new UserStoreException("Error while doPreAddUser", e);
        //                }

        // set recovery data
        RecoveryProcessor processor = new RecoveryProcessor();
        VerificationBean verificationBean = new VerificationBean();

        try {
          verificationBean =
              processor.updateConfirmationCode(1, userName, userStoreManager.getTenantId());
        } catch (IdentityException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }

        // preparing a bean to send the email
        UserIdentityMgtBean bean = new UserIdentityMgtBean();
        bean.setUserId(userName)
            .setConfirmationCode(verificationBean.getKey())
            .setRecoveryType(IdentityMgtConstants.Notification.TEMPORARY_PASSWORD)
            .setEmail(claims.get(config.getAccountRecoveryClaim()));

        UserRecoveryDTO recoveryDto = new UserRecoveryDTO(userName);
        recoveryDto.setNotification(IdentityMgtConstants.Notification.ASK_PASSWORD);
        recoveryDto.setNotificationType("EMAIL");
        recoveryDto.setTenantId(userStoreManager.getTenantId());
        recoveryDto.setConfirmationCode(verificationBean.getKey());

        NotificationDataDTO notificationDto = null;

        try {
          notificationDto = processor.recoverWithNotification(recoveryDto);
        } catch (IdentityException e) {
          if (log.isDebugEnabled()) {
            log.debug(e.getMessage());
          }
          throw new UserStoreException("Error while sending notification. " + e.getMessage());
        }

        if (notificationDto != null && notificationDto.isNotificationSent()) {
          return true;
        } else {
          return false;
        }

        // sending email
        //				UserIdentityManagementUtil.notifyViaEmail(bean);

      } else {
        // none-empty passwords. lock account and persist
        /*				This scenario needs to be validated.
        * 				userIdentityClaimsDO.setAccountLock(true)
        			                    .setPasswordTimeStamp(System.currentTimeMillis());
        			try {
        				UserIdentityManagementUtil.storeUserIdentityClaims(userIdentityClaimsDO, userStoreManager);
        			} catch (IdentityException e) {
        				throw new UserStoreException("Error while doPostAddUser", e);
        			}
        			String confirmationCode = UserIdentityManagementUtil.generateRandomConfirmationCode();
        			// store identity metadata
        			UserRecoveryDataDO metadataDO = new UserRecoveryDataDO();
        			metadataDO.setUserName(userName).setTenantId(userStoreManager.getTenantId())
        			          .setCode(confirmationCode);
        			try {
                        UserIdentityManagementUtil.storeUserIdentityMetadata(metadataDO);
                       } catch (IdentityException e) {
                       	throw new UserStoreException("Error while doPostAddUser", e);
                       }
        			// sending a mail with the confirmation code
        			UserIdentityMgtBean bean = new UserIdentityMgtBean();
        			bean.setUserId(userName)
        			    .setRecoveryType(IdentityMgtConstants.Notification.ACCOUNT_CONFORM)
        			    .setConfirmationCode(confirmationCode);
        			UserIdentityManagementUtil.notifyViaEmail(bean);
        			return true; */
      }
    }
    // No account recoveries are defined, no email will be sent.
    if (config.isAuthPolicyAccountLockOnCreation()) {
      // accounts are locked. Admin should unlock
      userIdentityClaimsDO.setAccountLock(true);
      userIdentityClaimsDO.setPasswordTimeStamp(System.currentTimeMillis());
      try {
        config.getIdentityDataStore().store(userIdentityClaimsDO, userStoreManager);
      } catch (IdentityException e) {
        throw new UserStoreException("Error while doPostAddUser", e);
      }
    }
    return true;
  }
  /**
   * This method locks the accounts after a configured number of authentication failure attempts.
   * And unlocks accounts based on successful authentications.
   */
  @Override
  public boolean doPostAuthenticate(
      String userName, boolean authenticated, UserStoreManager userStoreManager)
      throws UserStoreException {

    if (log.isDebugEnabled()) {
      log.debug("Post authenticator is called in IdentityMgtEventListener");
    }

    IdentityMgtConfig config = IdentityMgtConfig.getInstance();

    if (!config.isEnableAuthPolicy()) {
      return authenticated;
    }

    UserIdentityClaimsDO userIdentityDTO = module.load(userName, userStoreManager);
    if (userIdentityDTO == null) {
      userIdentityDTO = new UserIdentityClaimsDO(userName);
    }

    boolean userOTPEnabled = userIdentityDTO.getOneTimeLogin();

    // One time password check
    if (authenticated
        && config.isAuthPolicyOneTimePasswordCheck()
        && (!userStoreManager.isReadOnly())) {

      // reset password of the user and notify user of the new password
      if (userOTPEnabled) {

        String password = UserIdentityManagementUtil.generateTemporaryPassword().toString();
        userStoreManager.updateCredentialByAdmin(userName, password);

        // Get email user claim value
        String email =
            userStoreManager.getUserClaimValue(
                userName, UserCoreConstants.ClaimTypeURIs.EMAIL_ADDRESS, null);

        if (email == null) {
          throw new UserStoreException("No user email provided for user " + userName);
        }

        List<NotificationSendingModule> notificationModules =
            config.getNotificationSendingModules();

        if (notificationModules != null) {

          NotificationDataDTO notificationData = new NotificationDataDTO();

          NotificationData emailNotificationData = new NotificationData();
          String emailTemplate = null;
          int tenantId = userStoreManager.getTenantId();
          String firstName = null;
          try {
            firstName =
                Utils.getClaimFromUserStoreManager(
                    userName, tenantId, "http://wso2.org/claims/givenname");
          } catch (IdentityException e2) {
            throw new UserStoreException("Could not load user given name");
          }
          emailNotificationData.setTagData("first-name", firstName);
          emailNotificationData.setTagData("user-name", userName);
          emailNotificationData.setTagData("otp-password", password);

          emailNotificationData.setSendTo(email);

          Config emailConfig = null;
          ConfigBuilder configBuilder = ConfigBuilder.getInstance();
          try {
            emailConfig =
                configBuilder.loadConfiguration(ConfigType.EMAIL, StorageType.REGISTRY, tenantId);
          } catch (Exception e1) {
            throw new UserStoreException("Could not load the email template configuration");
          }

          emailTemplate = emailConfig.getProperty("otp");

          Notification emailNotification = null;
          try {
            emailNotification =
                NotificationBuilder.createNotification(
                    "EMAIL", emailTemplate, emailNotificationData);
          } catch (Exception e) {
            throw new UserStoreException("Could not create the email notification");
          }
          NotificationSender sender = new NotificationSender();

          for (NotificationSendingModule notificationSendingModule : notificationModules) {

            if (IdentityMgtConfig.getInstance().isNotificationInternallyManaged()) {
              notificationSendingModule.setNotificationData(notificationData);
              notificationSendingModule.setNotification(emailNotification);
              sender.sendNotification(notificationSendingModule);
              notificationData.setNotificationSent(true);
            }
          }

        } else {
          throw new UserStoreException("No notification modules configured");
        }
      }
    }

    // Password expire check. Not for OTP enabled users.
    if (authenticated
        && config.isAuthPolicyExpirePasswordCheck()
        && !userOTPEnabled
        && (!userStoreManager.isReadOnly())) {
      // TODO - password expire impl
      // Refactor adduser and change password api to stamp the time
      // Check user's expire time in the claim
      // if expired redirect to change password
      // else pass through
      /*
      long timestamp = userIdentityDTO.getPasswordTimeStamp();
      // Only allow behavior to users with this claim. Intent bypass for admin?
      if (timestamp > 0) {
      	Calendar passwordExpireTime = Calendar.getInstance();
      	passwordExpireTime.setTimeInMillis(timestamp);

      	int expireDuration = config.getAuthPolicyPasswordExpireTime();
      	if (expireDuration > 0) {

      		passwordExpireTime.add(Calendar.DATE, expireDuration);

      		Calendar currentTime = Calendar.getInstance();

      		if (currentTime.compareTo(passwordExpireTime) > 0) {
      			// password expired
      			// set flag to redirect
      			log.error("Password is expired ...........");
      			// throw new UserStoreException("Password is expired");
      		}

      	}
      }
      */
    }

    if (!authenticated && config.isAuthPolicyAccountLockOnFailure()) {
      userIdentityDTO.setFailAttempts();
      // reading the max allowed #of failure attempts

      if (userIdentityDTO.getFailAttempts() >= config.getAuthPolicyMaxLoginAttempts()) {
        if (log.isDebugEnabled()) {
          log.debug(
              "User, "
                  + userName
                  + " has exceed the max failed login attempts. "
                  + "User account would be locked");
        }
        userIdentityDTO.setAccountLock(true);
        // lock time from the config
        int lockTime = IdentityMgtConfig.getInstance().getAuthPolicyLockingTime();
        if (lockTime != 0) {
          userIdentityDTO.setUnlockTime(System.currentTimeMillis() + (lockTime * 60 * 1000));
        }
      }

      try {
        module.store(userIdentityDTO, userStoreManager);
      } catch (IdentityException e) {
        throw new UserStoreException("Error while doPostAuthenticate", e);
      }

    } else {
      // if the account was locked due to account verification process,
      // the unlock the account and reset the number of failedAttempts
      if (userIdentityDTO.isAccountLocked() || userIdentityDTO.getFailAttempts() > 0) {
        userIdentityDTO.setAccountLock(false);
        userIdentityDTO.setFailAttempts(0);
        userIdentityDTO.setUnlockTime(0);
        try {
          module.store(userIdentityDTO, userStoreManager);
        } catch (IdentityException e) {
          throw new UserStoreException("Error while doPostAuthenticate", e);
        }
      }
    }

    return true;
  }