package com.airlock.iam.flow.shared.domain.model.identification;

import com.airlock.iam.authentication.api.domain.model.step.IdentityProvidingFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.UserIdentifyingStep;
import com.airlock.iam.authentication.api.domain.service.username.IdentificationResult;
import com.airlock.iam.authentication.api.domain.service.username.IdentificationResultConverter;
import com.airlock.iam.authentication.api.domain.service.username.IdentityProvidingService;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
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.domain.model.ValidationStepResultData;
import com.airlock.iam.flow.shared.application.configuration.identification.UserIdentifyingStepConfig;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;

@LogicForConfig(UserIdentifyingStepConfig.class)
public final class UserIdentifyingStepImpl implements UserIdentifyingStep, IdentityProvidingFlowStep {
   private final Provider<IdentityProvidingService> identificationService;
   private final Provider<SigningLogger> signingLogger;
   private final Provider<HelpdeskLogger> helpdeskLogger;

   @Inject
   public UserIdentifyingStepImpl(
      Provider<IdentityProvidingService> identificationService, Provider<SigningLogger> signingLogger, Provider<HelpdeskLogger> helpdeskLogger
   ) {
      this.signingLogger = signingLogger;
      this.helpdeskLogger = helpdeskLogger;
      this.identificationService = identificationService;
   }

   @Override
   public StepResult identifyUser(String providedUsername) {
      if (providedUsername.isBlank()) {
         return this.stepResultFactory()
            .failRetry(GenericStepError.VALIDATION_FAILED, UserIdentifyingStep.UserIdentifyingStepAction.USERNAME_REQUIRED)
            .resultData(new ValidationStepResultData(ValidationResult.singleError("username", GenericError.INVALID_VALUE)))
            .build();
      } else {
         IdentificationResult identifyResult = ((IdentityProvidingService)this.identificationService.get())
            .tryIdentify(ProvidedUsername.providedUsernameOf(providedUsername));
         if (identifyResult.isFailure() && !identifyResult.hasError(IdentificationResult.IdentificationFailure.IdentificationError.USER_NOT_FOUND)) {
            IdentificationResult.IdentificationFailure failure = identifyResult.getFailure().orElseThrow();
            identifyResult.getUserIdentity().ifPresent(id -> this.logFailure(id, failure.error()));
            return IdentificationResultConverter.convertToStepResult(
                  failure, this.stepResultFactory(), UserIdentifyingStep.UserIdentifyingStepAction.USERNAME_REQUIRED
               )
               .build();
         } else {
            UserIdentity userIdentity = identifyResult.getUserIdentity().orElseThrow();
            if (identifyResult.hasError(IdentificationResult.IdentificationFailure.IdentificationError.USER_NOT_FOUND)) {
               ((SigningLogger)this.signingLogger.get()).log("Identification of user '%s' failed, user not found.", new Object[]{userIdentity.id()});
               return this.stepResultFactory()
                  .failRetry(UserIdentifyingStep.UserIdentifyingStepError.USER_NOT_FOUND, UserIdentifyingStep.UserIdentifyingStepAction.USERNAME_REQUIRED)
                  .build();
            } else {
               this.logSuccess(userIdentity);
               return this.userIdentifiedSuccessResult(userIdentity);
            }
         }
      }
   }

   private void logSuccess(UserIdentity userIdentity) {
      ((SigningLogger)this.signingLogger.get()).log("User '%s' was successfully identified.", new Object[]{userIdentity.id()});
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userIdentity.id(), "User ${affectedUser} was successfully identified.");
   }

   private void logFailure(UserIdentity userIdentity, IdentificationResult.IdentificationFailure.IdentificationError error) {
      String logMessage = "Identification of user '%s' failed, additional information: %s";
      ((SigningLogger)this.signingLogger.get()).log(logMessage, new Object[]{LogUtil.logSafeString(userIdentity.id()), error});
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(userIdentity.id(), logMessage, new Object[]{LogUtil.logSafeString(userIdentity.id()), error});
   }

   @Override
   public StepResult initialize() {
      return this.stepResultFactory().stay(UserIdentifyingStep.UserIdentifyingStepAction.USERNAME_REQUIRED).build();
   }

   @Override
   public String getDescription() {
      return "User Identifying Step";
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class UserIdentifyingStepImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<IdentityProvidingService> identificationService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<SigningLogger> signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<HelpdeskLogger> helpdeskLogger;

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingStepImpl.UserIdentifyingStepImplBuilder identificationService(Provider<IdentityProvidingService> identificationService) {
         this.identificationService = identificationService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingStepImpl.UserIdentifyingStepImplBuilder signingLogger(Provider<SigningLogger> signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingStepImpl.UserIdentifyingStepImplBuilder helpdeskLogger(Provider<HelpdeskLogger> helpdeskLogger) {
         this.helpdeskLogger = helpdeskLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingStepImpl build() {
         return new UserIdentifyingStepImpl(this.identificationService, this.signingLogger, this.helpdeskLogger);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "UserIdentifyingStepImpl.UserIdentifyingStepImplBuilder(identificationService="
            + this.identificationService
            + ", signingLogger="
            + this.signingLogger
            + ", helpdeskLogger="
            + this.helpdeskLogger
            + ")";
      }
   }
}
