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

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.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.result.Result;
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.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.domain.model.ValidationStepResultData;
import com.airlock.iam.flow.domain.model.step.DynamicStepError;
import com.airlock.iam.flow.shared.domain.model.userdata.IdentificationByUserDataFailure;
import com.airlock.iam.flow.shared.domain.service.identification.IdentityProvidingByUserDataService;
import com.airlock.iam.publicselfservice.api.domain.model.PublicSelfServiceRestriction;
import com.airlock.iam.publicselfservice.application.configuration.steps.PublicSelfServiceUserByDataIdentifyingStepConfig;
import com.airlock.iam.publicselfservice.domain.service.RestrictionViolationsService;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

@LogicForConfig(PublicSelfServiceUserByDataIdentifyingStepConfig.class)
public class PublicSelfServiceUserByDataIdentifyingStep implements UserIdentifyingStep, IdentityProvidingFlowStep {
   private final Provider<IdentityProvidingByUserDataService> identificationService;
   private final Provider<RestrictionViolationsService> restrictionViolationsService;

   @Inject
   public PublicSelfServiceUserByDataIdentifyingStep(
      Provider<IdentityProvidingByUserDataService> identificationService, Provider<RestrictionViolationsService> restrictionViolationsService
   ) {
      this.identificationService = identificationService;
      this.restrictionViolationsService = restrictionViolationsService;
   }

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

   @Override
   public StepResult identifyUser(String providedUsername) {
      return this.identifyUserByUserData(Collections.emptyMap());
   }

   @Override
   public StepResult identifyUserByUserData(Map<String, String> userData) {
      Result<IdentificationResult, IdentificationByUserDataFailure> result = ((IdentityProvidingByUserDataService)this.identificationService.get())
         .tryIdentifyByUserData(userData);
      if (result.isFailure()) {
         if (result.getFailure().hasError(IdentificationByUserDataFailure.IdentificationByUserDataError.USER_DATA_INVALID)
            || result.getFailure().hasError(IdentificationByUserDataFailure.IdentificationByUserDataError.USER_DATA_MISSING)) {
            ValidationResult validationResult = result.getFailure()
               .validationResult()
               .orElseThrow(() -> new FailureException("Expected validation result to be present"));
            return this.getValidationErrorStepResult(validationResult);
         }
      } else if (result.get().isFailure()
         && (
            result.get().hasError(IdentificationResult.IdentificationFailure.IdentificationError.CONCURRENT_ACCESS)
               || result.get().hasError(IdentificationResult.IdentificationFailure.IdentificationError.DIFFERENT_USERNAME)
         )) {
         return IdentificationResultConverter.convertToStepResult(
               result.get().getFailure().orElseThrow(), this.stepResultFactory(), UserIdentifyingStep.UserIdentifyingStepAction.USER_DATA_REQUIRED
            )
            .build();
      }

      UserIdentity identity;
      if (result.peek().isPresent() && result.get().getUserIdentity().isPresent()) {
         identity = result.get().getUserIdentity().get();
      } else {
         identity = ((IdentityProvidingByUserDataService)this.identificationService.get()).createFakeIdentity();
      }

      Optional<PublicSelfServiceRestriction.ViolationCode> violationToReport = ((RestrictionViolationsService)this.restrictionViolationsService.get())
         .checkViolations(identity);
      return violationToReport.isPresent()
         ? this.stepResultFactory().fail(DynamicStepError.dynamicStepError(violationToReport.get().name())).build()
         : this.userIdentifiedSuccessResult(identity);
   }

   @Override
   public StepAction stepAction() {
      return UserIdentifyingStep.UserIdentifyingStepAction.USER_DATA_REQUIRED;
   }

   private StepResult getValidationErrorStepResult(ValidationResult validationResult) {
      return this.stepResultFactory()
         .failRetry(GenericStepError.VALIDATION_FAILED, UserIdentifyingStep.UserIdentifyingStepAction.USER_DATA_REQUIRED)
         .resultData(new ValidationStepResultData(validationResult))
         .build();
   }

   @Override
   public String getDescription() {
      return "Identify User By User Data Step";
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class PublicSelfServiceUserByDataIdentifyingStepBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<IdentityProvidingByUserDataService> identificationService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<RestrictionViolationsService> restrictionViolationsService;

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

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUserByDataIdentifyingStep.PublicSelfServiceUserByDataIdentifyingStepBuilder identificationService(
         Provider<IdentityProvidingByUserDataService> identificationService
      ) {
         this.identificationService = identificationService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUserByDataIdentifyingStep.PublicSelfServiceUserByDataIdentifyingStepBuilder restrictionViolationsService(
         Provider<RestrictionViolationsService> restrictionViolationsService
      ) {
         this.restrictionViolationsService = restrictionViolationsService;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "PublicSelfServiceUserByDataIdentifyingStep.PublicSelfServiceUserByDataIdentifyingStepBuilder(identificationService="
            + this.identificationService
            + ", restrictionViolationsService="
            + this.restrictionViolationsService
            + ")";
      }
   }
}
