package com.airlock.iam.publicselfservice.domain.model.step;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.e2ee.E2eEncryptionSession;
import com.airlock.iam.common.api.domain.model.password.Password;
import com.airlock.iam.common.api.domain.model.password.PasswordPolicyViolation;
import com.airlock.iam.common.domain.model.password.UserPassword;
import com.airlock.iam.common.domain.model.password.event.UserPasswordEventFactory;
import com.airlock.iam.common.domain.model.password.policy.UserPasswordPolicy;
import com.airlock.iam.common.domain.model.password.repository.UserPasswordRepository;
import com.airlock.iam.common.domain.model.password.reset.PasswordResetResult;
import com.airlock.iam.common.domain.service.token.UserSessionTokenService;
import com.airlock.iam.core.misc.authen.PasswordPolicyCheckResult;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultBuilder;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.shared.domain.model.password.PasswordPolicyViolationsData;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.publicselfservice.api.domain.model.PublicSelfServiceUser;
import com.airlock.iam.publicselfservice.application.configuration.steps.PasswordResetStepConfig;
import com.airlock.iam.publicselfservice.domain.model.flow.session.PublicSelfServiceSetPasswordStepSession;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Optional;

@LogicForConfig(PasswordResetStepConfig.class)
public final class PasswordResetStepImpl implements PasswordResetStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(PasswordResetStepImpl.class);
   private final Provider<PublicSelfServiceUser> user;
   private final Provider<UserPasswordRepository> passwordRepository;
   private final Provider<UserPasswordPolicy> passwordPolicy;
   private final Provider<PublicSelfServiceSetPasswordStepSession> stepSession;
   private final Provider<UserSessionTokenService> userSessionTokenService;
   private final Provider<UserPasswordEventFactory> eventFactory;
   private final SigningLogger signingLogger;
   private final HelpdeskLogger helpdeskLogger;

   @Inject
   public PasswordResetStepImpl(
      Provider<PublicSelfServiceUser> user,
      Provider<UserPasswordRepository> passwordRepository,
      Provider<UserPasswordPolicy> passwordPolicy,
      Provider<PublicSelfServiceSetPasswordStepSession> stepSession,
      Provider<UserSessionTokenService> userSessionTokenService,
      Provider<UserPasswordEventFactory> eventFactory,
      SigningLogger signingLogger,
      HelpdeskLogger helpdeskLogger
   ) {
      this.user = user;
      this.passwordRepository = passwordRepository;
      this.passwordPolicy = passwordPolicy;
      this.eventFactory = eventFactory;
      this.signingLogger = signingLogger;
      this.stepSession = stepSession;
      this.userSessionTokenService = userSessionTokenService;
      this.helpdeskLogger = helpdeskLogger;
   }

   @Override
   public StepResult initialize() {
      if (!((PublicSelfServiceUser)this.user.get()).isPublicSelfServiceAllowed()) {
         return this.stepResultFactory().fail(PasswordResetStep.SetPasswordStepErrors.NOT_ALLOWED).build();
      }

      StepResultBuilder stepResult = this.stepResultFactory().stay(PasswordResetStep.SetPasswordStepActions.NEW_PASSWORD_REQUIRED);
      this.addE2eEncryptionMetadataToStepResult(stepResult);
      return stepResult.build();
   }

   @Override
   public StepResult setPassword(Password newPassword) {
      if (!((PublicSelfServiceUser)this.user.get()).isPublicSelfServiceAllowed()) {
         return this.stepResultFactory().fail(PasswordResetStep.SetPasswordStepErrors.NOT_ALLOWED).build();
      }

      StepResult stepResult = this.trySetPassword(newPassword);
      if (stepResult.isFailure()) {
         return stepResult;
      }

      this.signingLogger.log("User " + this.quotedName() + " successfully set a new password.");
      UserId userId = ((PublicSelfServiceUser)this.user.get()).getIdentity().id();
      this.helpdeskLogger.log(userId, "Successfully set a new password in public self-service flow.");
      ((UserPasswordEventFactory)this.eventFactory.get()).publishPasswordChangedEvent(userId);
      return stepResult;
   }

   private StepResult trySetPassword(Password newPassword) {
      UserPassword userPassword = ((UserPasswordRepository)this.passwordRepository.get())
         .getUserPassword(((PublicSelfServiceUser)this.user.get()).getIdentity());
      PasswordPolicyCheckResult policyCheckResult = userPassword.checkPolicy(newPassword, (UserPasswordPolicy)this.passwordPolicy.get());
      if (!policyCheckResult.isValid()) {
         LOG.info("Not setting new password for user " + this.quotedName() + " because of password policy violations.");
         return this.failWithPolicyViolations(policyCheckResult.getViolations());
      }

      PasswordResetResult setResult = userPassword.setPassword(newPassword);
      if (setResult.isDecryptionFailed()) {
         return this.failWithDecryptionFailed();
      }

      if (!setResult.isSuccessful()) {
         return this.failWithPolicyViolations(setResult.getPolicyViolations());
      }

      boolean changed = userPassword.save();
      if (changed) {
         ((PublicSelfServiceUser)this.user.get()).reloadPrincipal();
      }

      ((UserSessionTokenService)this.userSessionTokenService.get()).handlePasswordChange(((PublicSelfServiceUser)this.user.get()).getIdentity().id());
      return this.stepResultFactory().success().build();
   }

   private StepResult failWithPolicyViolations(List<PasswordPolicyViolation> violations) {
      ((UserPasswordRepository)this.passwordRepository.get()).handleFailedAction();
      StepResultBuilder stepResult = this.stepResultFactory()
         .failRetry(PasswordResetStep.SetPasswordStepErrors.PASSWORD_POLICY_VIOLATED, PasswordResetStep.SetPasswordStepActions.NEW_PASSWORD_REQUIRED)
         .resultData(new PasswordPolicyViolationsData(violations));
      this.addE2eEncryptionMetadataToStepResult(stepResult);
      return stepResult.build();
   }

   private StepResult failWithDecryptionFailed() {
      ((UserPasswordRepository)this.passwordRepository.get()).handleFailedAction();
      StepResultBuilder stepResult = this.stepResultFactory()
         .failRetry(GenericStepError.DECRYPTION_FAILED, PasswordResetStep.SetPasswordStepActions.NEW_PASSWORD_REQUIRED);
      this.addE2eEncryptionMetadataToStepResult(stepResult);
      return stepResult.build();
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(
         new PublicSelfServiceSetPasswordStepSession(
            ((UserPasswordRepository)this.passwordRepository.get()).createNewE2eEncryptionSession(E2eEncryptionSession.E2eEncryptionUseCase.PASSWORD_RESET)
         )
      );
   }

   private Optional<E2eEncryptionSession> getE2eEncryptionSession() {
      return ((PublicSelfServiceSetPasswordStepSession)this.stepSession.get()).getE2eEncryptionSession();
   }

   private void addE2eEncryptionMetadataToStepResult(StepResultBuilder builder) {
      this.getE2eEncryptionSession().ifPresent(session -> builder.addAdditionalAttribute("e2eEncryptionInformation", session.getPublicEncryptionInformation()));
   }

   @Override
   public String getDescription() {
      return "Password Reset (Public Self-Service)";
   }

   private String quotedName() {
      return LogUtil.quote(((PublicSelfServiceUser)this.user.get()).getIdentity().id());
   }

   @SuppressFBWarnings(justification = "generated code")
   public static PasswordResetStepImpl.PasswordResetStepImplBuilder builder() {
      return new PasswordResetStepImpl.PasswordResetStepImplBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class PasswordResetStepImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<PublicSelfServiceUser> user;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserPasswordRepository> passwordRepository;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserPasswordPolicy> passwordPolicy;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<PublicSelfServiceSetPasswordStepSession> stepSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserSessionTokenService> userSessionTokenService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserPasswordEventFactory> eventFactory;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private HelpdeskLogger helpdeskLogger;

      @SuppressFBWarnings(justification = "generated code")
      PasswordResetStepImplBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder user(Provider<PublicSelfServiceUser> user) {
         this.user = user;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder passwordRepository(Provider<UserPasswordRepository> passwordRepository) {
         this.passwordRepository = passwordRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder passwordPolicy(Provider<UserPasswordPolicy> passwordPolicy) {
         this.passwordPolicy = passwordPolicy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder stepSession(Provider<PublicSelfServiceSetPasswordStepSession> stepSession) {
         this.stepSession = stepSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder userSessionTokenService(Provider<UserSessionTokenService> userSessionTokenService) {
         this.userSessionTokenService = userSessionTokenService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder eventFactory(Provider<UserPasswordEventFactory> eventFactory) {
         this.eventFactory = eventFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder signingLogger(SigningLogger signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl.PasswordResetStepImplBuilder helpdeskLogger(HelpdeskLogger helpdeskLogger) {
         this.helpdeskLogger = helpdeskLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PasswordResetStepImpl build() {
         return new PasswordResetStepImpl(
            this.user,
            this.passwordRepository,
            this.passwordPolicy,
            this.stepSession,
            this.userSessionTokenService,
            this.eventFactory,
            this.signingLogger,
            this.helpdeskLogger
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "PasswordResetStepImpl.PasswordResetStepImplBuilder(user="
            + this.user
            + ", passwordRepository="
            + this.passwordRepository
            + ", passwordPolicy="
            + this.passwordPolicy
            + ", stepSession="
            + this.stepSession
            + ", userSessionTokenService="
            + this.userSessionTokenService
            + ", eventFactory="
            + this.eventFactory
            + ", signingLogger="
            + this.signingLogger
            + ", helpdeskLogger="
            + this.helpdeskLogger
            + ")";
      }
   }
}
