package com.airlock.iam.saml2.domain.model.sp.step;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationFlowStep;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResult;
import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultFactory;
import com.airlock.iam.authentication.api.domain.service.username.IdentificationResult;
import com.airlock.iam.authentication.api.domain.service.username.IdentityProvidingService;
import com.airlock.iam.authentication.impl.domain.model.authentication.UserIdentificationResultData;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.authmethod.AuthenticationMethod;
import com.airlock.iam.common.api.domain.model.user.ProvidedUsername;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.transform.UsernameTransformerUsernameTransformation;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactorDetail;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.api.domain.model.step.GenericStepAction;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.flow.shared.domain.model.attributes.AuthTokenIdFlowSessionAttribute;
import com.airlock.iam.flow.shared.domain.model.attributes.AuthenticationAuditTokenFlowAttribute;
import com.airlock.iam.log.api.helpdesk.HelpdeskLogger;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.saml2.application.configuration.sp.Saml2FlowSpConfig;
import com.airlock.iam.saml2.application.configuration.sp.Saml2UserIdentifyingStepConfig;
import com.airlock.iam.saml2.domain.model.assertion.attribute.Saml2AssertionAttributesImporter;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpAssertionAttributes;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpEntityId;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpInitiatedSsoId;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpInitiatedSsoSessionAttribute;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpInitiatedSsoUriFactory;
import com.airlock.iam.saml2.domain.service.Saml2FlowSpConfigService;
import com.airlock.iam.saml2.domain.service.Saml2TagsFromAttributesService;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

@LogicForConfig(Saml2UserIdentifyingStepConfig.class)
public final class Saml2UserIdentifyingStepImpl implements Saml2UserIdentifyingStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(Saml2UserIdentifyingStepImpl.class);
   private static final String NAVIGATION_REQUIRED_LOCATION_ATTRIBUTE_NAME = "location";
   private final Provider<Saml2SpInitiatedSsoUriFactory> spInitiatedSsoUriFactory;
   private final Provider<Saml2SpInitiatedSsoId> spInitiatedSsoId;
   private final Provider<UserSession> userSession;
   private final Provider<Saml2FlowSpConfigService> spConfigService;
   private final Provider<Saml2SpEntityId> spEntityIdForSpInitiatedSso;
   private final Provider<AuthenticationMethod> authMethod;
   private final Provider<IdentityProvidingService> identificationService;
   private final Provider<SigningLogger> auditLogger;
   private final Provider<HelpdeskLogger> helpdeskLogger;
   private final Provider<FlowAttributeRepository> attributesRepository;
   private final Provider<FlowSession> flowSession;
   private final Provider<Saml2AssertionAttributesImporter> attributesImporter;
   private final Provider<Saml2TagsFromAttributesService> tagsFromAttributesService;
   private final Provider<FlowFederationSessionRepository> federationSessionRepository;

   @Inject
   Saml2UserIdentifyingStepImpl(
      @NonNull Provider<Saml2SpInitiatedSsoUriFactory> spInitiatedSsoUriFactory,
      @NonNull Provider<Saml2SpInitiatedSsoId> spInitiatedSsoId,
      @NonNull Provider<UserSession> userSession,
      @NonNull Provider<Saml2FlowSpConfigService> spConfigService,
      @NonNull Provider<Saml2SpEntityId> spEntityIdForSpInitiatedSso,
      @NonNull Provider<AuthenticationMethod> authMethod,
      @NonNull Provider<IdentityProvidingService> identificationService,
      @NonNull Provider<SigningLogger> auditLogger,
      @NonNull Provider<HelpdeskLogger> helpdeskLogger,
      @NonNull Provider<FlowAttributeRepository> attributesRepository,
      @NonNull Provider<FlowSession> flowSession,
      @NonNull Provider<Saml2AssertionAttributesImporter> attributesImporter,
      @NonNull Provider<Saml2TagsFromAttributesService> tagsFromAttributesService,
      @NonNull Provider<FlowFederationSessionRepository> federationSessionRepository
   ) {
      if (spInitiatedSsoUriFactory == null) {
         throw new NullPointerException("spInitiatedSsoUriFactory is marked non-null but is null");
      }

      if (spInitiatedSsoId == null) {
         throw new NullPointerException("spInitiatedSsoId is marked non-null but is null");
      }

      if (userSession == null) {
         throw new NullPointerException("userSession is marked non-null but is null");
      }

      if (spConfigService == null) {
         throw new NullPointerException("spConfigService is marked non-null but is null");
      }

      if (spEntityIdForSpInitiatedSso == null) {
         throw new NullPointerException("spEntityIdForSpInitiatedSso is marked non-null but is null");
      }

      if (authMethod == null) {
         throw new NullPointerException("authMethod is marked non-null but is null");
      }

      if (identificationService == null) {
         throw new NullPointerException("identificationService is marked non-null but is null");
      }

      if (auditLogger == null) {
         throw new NullPointerException("auditLogger is marked non-null but is null");
      }

      if (helpdeskLogger == null) {
         throw new NullPointerException("helpdeskLogger is marked non-null but is null");
      }

      if (attributesRepository == null) {
         throw new NullPointerException("attributesRepository is marked non-null but is null");
      }

      if (flowSession == null) {
         throw new NullPointerException("flowSession is marked non-null but is null");
      }

      if (attributesImporter == null) {
         throw new NullPointerException("attributesImporter is marked non-null but is null");
      }

      if (tagsFromAttributesService == null) {
         throw new NullPointerException("tagsFromAttributesService is marked non-null but is null");
      }

      if (federationSessionRepository == null) {
         throw new NullPointerException("federationSessionRepository is marked non-null but is null");
      }

      this.spInitiatedSsoUriFactory = spInitiatedSsoUriFactory;
      this.spInitiatedSsoId = spInitiatedSsoId;
      this.userSession = userSession;
      this.spConfigService = spConfigService;
      this.spEntityIdForSpInitiatedSso = spEntityIdForSpInitiatedSso;
      this.authMethod = authMethod;
      this.identificationService = identificationService;
      this.auditLogger = auditLogger;
      this.helpdeskLogger = helpdeskLogger;
      this.attributesRepository = attributesRepository;
      this.flowSession = flowSession;
      this.attributesImporter = attributesImporter;
      this.tagsFromAttributesService = tagsFromAttributesService;
      this.federationSessionRepository = federationSessionRepository;
   }

   @Override
   public AuthenticationStepResult initialize() {
      Optional<Saml2IdpInitiatedSsoFlowAttribute> idpInitiatedSsoAttribute = ((FlowAttributeRepository)this.attributesRepository.get())
         .tryGetAttribute(Saml2IdpInitiatedSsoFlowAttribute.class);
      return idpInitiatedSsoAttribute.isPresent() ? this.idpInitiatedSsoIdentify(idpInitiatedSsoAttribute.get()) : this.spInitiatedSsoInitialize();
   }

   private AuthenticationStepResult idpInitiatedSsoIdentify(Saml2IdpInitiatedSsoFlowAttribute idpInitiatedSsoAttribute) {
      AuthenticationStepResult result = this.identify(idpInitiatedSsoAttribute.attributes());
      if (result.isSuccess()) {
         ((FlowSession)this.flowSession.get()).takeDownRedFlag(SAML2_IDP_INITIATED_SSO_USER_IDENTIFY_REQUIRED_RED_FLAG);
      }

      return result;
   }

   private AuthenticationStepResult spInitiatedSsoInitialize() {
      this.ensureSaml2SpConfigured((Saml2SpEntityId)this.spEntityIdForSpInitiatedSso.get());
      Saml2SpInitiatedSsoId ssoId = (Saml2SpInitiatedSsoId)this.spInitiatedSsoId.get();
      ((UserSession)this.userSession.get())
         .addUserSessionAttribute(
            Saml2SpInitiatedSsoSessionAttribute.spInitiatedSsoSessionAttribute(ssoId, (Saml2SpEntityId)this.spEntityIdForSpInitiatedSso.get())
         );
      return this.stepResultFactory()
         .stay(GenericStepAction.NAVIGATION_REQUIRED)
         .addAdditionalAttribute("location", ((Saml2SpInitiatedSsoUriFactory)this.spInitiatedSsoUriFactory.get()).create(ssoId))
         .build();
   }

   private Saml2FlowSpConfig ensureSaml2SpConfigured(Saml2SpEntityId spEntityId) {
      Optional<Saml2FlowSpConfig> spFlowConfig = ((Saml2FlowSpConfigService)this.spConfigService.get()).findFlowSpConfig(spEntityId);
      if (spFlowConfig.isEmpty()) {
         throw new FailureException(
            "The SAML 2.0 SP with entity ID \""
               + spEntityId.value()
               + "\" of the User Identifying Step was not found in the Loginapp's SAML SP Settings (Flow Auth)"
         );
      } else {
         return spFlowConfig.get();
      }
   }

   public AuthenticationStepResult identify(Saml2SpAssertionAttributes ssoAttributes) {
      Saml2FlowSpConfig spConfig = this.ensureSaml2SpConfigured(ssoAttributes.spEntityId());
      ProvidedUsername providedUsername = this.determineProvidedUsername(ssoAttributes, spConfig.getAttributeToImportAsUserId());
      IdentificationResult identificationResult = ((IdentityProvidingService)this.identificationService.get())
         .tryIdentify(providedUsername, new UsernameTransformerUsernameTransformation(spConfig.getUsernameTransformers()));
      if (identificationResult.isFailure()) {
         identificationResult.getUserIdentity()
            .ifPresentOrElse(
               id -> this.logIdentificationFailure(id, identificationResult.getFailure().orElseThrow().error()),
               () -> this.logIdentificationFailure(providedUsername)
            );
         return this.stepResultFactory().fail(AuthenticationFlowStep.GeneralAuthenticationStepError.AUTHENTICATION_FAILED).build();
      } else {
         UserIdentity authenticatedUserIdentity = identificationResult.getUserIdentity().orElseThrow();
         ((FlowFederationSessionRepository)this.federationSessionRepository.get())
            .getFederationSession()
            .orElseThrow(() -> new FailureException("No federation session available"))
            .setAuthenticatedUserId(authenticatedUserIdentity.id());
         this.logIdentificationSuccess(authenticatedUserIdentity);
         this.importAssertionAttributes(ssoAttributes);
         this.importAuthTokenId(ssoAttributes, spConfig.getAttributeToImportAsAuthTokenId());
         this.importAuditTokenFromAssertionAttributes(ssoAttributes, spConfig.getAttributeToImportAsAuditToken());
         return this.stepResultFactory()
            .success()
            .obtainedTags(((Saml2TagsFromAttributesService)this.tagsFromAttributesService.get()).getTags(ssoAttributes))
            .internalResultData(new UserIdentificationResultData(authenticatedUserIdentity))
            .build();
      }
   }

   private ProvidedUsername determineProvidedUsername(Saml2SpAssertionAttributes ssoAttributes, Optional<String> attributeToImportAsUserId) {
      if (attributeToImportAsUserId.isEmpty()) {
         ProvidedUsername providedUsername = ssoAttributes.providedUserIdFromNameID();
         LOG.info("Using user ID %s from NameID attribute".formatted(LogUtil.logSafeQuote(providedUsername)));
         return providedUsername;
      } else {
         ProvidedUsername providedUsername = ssoAttributes.attributes()
            .getSingleValueAttribute(attributeToImportAsUserId.get())
            .filter(StringUtils::isNotBlank)
            .map(ProvidedUsername::providedUsernameOf)
            .orElseThrow(
               () -> new ViolationException(
                  "Failed to extract user ID from attribute %s: attribute was not present in Assertion"
                     .formatted(LogUtil.logSafeQuote(attributeToImportAsUserId.get()))
               )
            );
         LOG.info(
            "Using user ID %s from configured attribute %s"
               .formatted(LogUtil.logSafeQuote(providedUsername), LogUtil.logSafeQuote(attributeToImportAsUserId.get()))
         );
         return providedUsername;
      }
   }

   private void logIdentificationFailure(UserIdentity id, IdentificationResult.IdentificationFailure.IdentificationError error) {
      UserId iamUserId = id.id();
      String logMessage = String.format("Identification of user %s failed. ", LogUtil.logSafeQuote(iamUserId))
         + String.format("Additional information: %s", error);
      ((SigningLogger)this.auditLogger.get()).log(logMessage);
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(iamUserId, logMessage);
   }

   private void logIdentificationFailure(ProvidedUsername providedUsername) {
      ((SigningLogger)this.auditLogger.get()).log("Failed to identify user with provided username %s".formatted(LogUtil.logSafeQuote(providedUsername)));
   }

   private void logIdentificationSuccess(UserIdentity user) {
      UserId iamUserId = user.id();
      String message = String.format("User %s successfully authenticated via SAML 2.0 SSO", LogUtil.logSafeQuote(iamUserId));
      ((SigningLogger)this.auditLogger.get()).log(message);
      ((HelpdeskLogger)this.helpdeskLogger.get()).log(iamUserId, message);
   }

   private void importAssertionAttributes(Saml2SpAssertionAttributes assertionAttributes) {
      ((Saml2AssertionAttributesImporter)this.attributesImporter.get()).importInFlow(assertionAttributes.attributes());
   }

   private void importAuthTokenId(Saml2SpAssertionAttributes ssoAttributes, Optional<String> attributeToImportAsAuthTokenId) {
      attributeToImportAsAuthTokenId.<String>flatMap(authTokenIdAttributeName -> ssoAttributes.attributes().getSingleValueAttribute(authTokenIdAttributeName))
         .map(AuthTokenIdFlowSessionAttribute::authTokenIdFlowSessionAttribute)
         .ifPresent(authTokenIdAttribute -> {
            this.logImportAuthTokenId(attributeToImportAsAuthTokenId.orElseThrow(), authTokenIdAttribute);
            ((FlowAttributeRepository)this.attributesRepository.get()).putOrMergeAttribute(authTokenIdAttribute);
         });
   }

   private void logImportAuthTokenId(String attributeToImportAsAuthTokenId, AuthTokenIdFlowSessionAttribute authTokenIdAttribute) {
      LOG.info(
         String.format(
            "Imported Auth Token ID with value %s from attribute %s",
            LogUtil.logSafeQuote(authTokenIdAttribute.getAuthTokenId()),
            LogUtil.logSafeQuote(attributeToImportAsAuthTokenId)
         )
      );
   }

   private void importAuditTokenFromAssertionAttributes(Saml2SpAssertionAttributes assertionAttributes, Optional<String> attributeToImportAsAuditToken) {
      if (!attributeToImportAsAuditToken.isEmpty()) {
         Optional<String> auditTokenAttributeValue = assertionAttributes.attributes().getSingleValueAttribute(attributeToImportAsAuditToken.get());
         if (!auditTokenAttributeValue.isEmpty()) {
            this.logReadAuditTokenValueFromAssertion(auditTokenAttributeValue.get());
            ((FlowAttributeRepository)this.attributesRepository.get())
               .putOrMergeAttribute(AuthenticationAuditTokenFlowAttribute.authenticationAuditTokenMultiFlowSessionFlowAttribute(auditTokenAttributeValue.get()));
         }
      }
   }

   private void logReadAuditTokenValueFromAssertion(String auditTokenAttributeValue) {
      LOG.info(
         String.format(
            "Read audit token with value %s from the assertion. Note that if an audit token has already been determined (e.g. if another authentication flow has already taken place), this value will be ignored.",
            LogUtil.logSafeQuote(auditTokenAttributeValue)
         )
      );
   }

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

   public AuthenticationStepResultFactory stepResultFactory() {
      return AuthenticationStepResultFactory.forFactorDetail(KnownAuthenticationFactorDetail.TOKEN_SAML);
   }

   @Override
   public AuthenticationMethod getAuthenticationMethod() {
      return (AuthenticationMethod)this.authMethod.get();
   }

   @Override
   public void finalizeStep() {
      ((UserSession)this.userSession.get()).removeUserSessionAttribute(Saml2SpInitiatedSsoSessionAttribute.class);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2UserIdentifyingStepImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2SpInitiatedSsoUriFactory> spInitiatedSsoUriFactory;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2SpInitiatedSsoId> spInitiatedSsoId;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserSession> userSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2FlowSpConfigService> spConfigService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2SpEntityId> spEntityIdForSpInitiatedSso;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<AuthenticationMethod> authMethod;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<IdentityProvidingService> identificationService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<SigningLogger> auditLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<HelpdeskLogger> helpdeskLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<FlowAttributeRepository> attributesRepository;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<FlowSession> flowSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2AssertionAttributesImporter> attributesImporter;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Saml2TagsFromAttributesService> tagsFromAttributesService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<FlowFederationSessionRepository> federationSessionRepository;

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

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder spInitiatedSsoUriFactory(
         @NonNull Provider<Saml2SpInitiatedSsoUriFactory> spInitiatedSsoUriFactory
      ) {
         if (spInitiatedSsoUriFactory == null) {
            throw new NullPointerException("spInitiatedSsoUriFactory is marked non-null but is null");
         }

         this.spInitiatedSsoUriFactory = spInitiatedSsoUriFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder spInitiatedSsoId(@NonNull Provider<Saml2SpInitiatedSsoId> spInitiatedSsoId) {
         if (spInitiatedSsoId == null) {
            throw new NullPointerException("spInitiatedSsoId is marked non-null but is null");
         }

         this.spInitiatedSsoId = spInitiatedSsoId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder userSession(@NonNull Provider<UserSession> userSession) {
         if (userSession == null) {
            throw new NullPointerException("userSession is marked non-null but is null");
         }

         this.userSession = userSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder spConfigService(@NonNull Provider<Saml2FlowSpConfigService> spConfigService) {
         if (spConfigService == null) {
            throw new NullPointerException("spConfigService is marked non-null but is null");
         }

         this.spConfigService = spConfigService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder spEntityIdForSpInitiatedSso(
         @NonNull Provider<Saml2SpEntityId> spEntityIdForSpInitiatedSso
      ) {
         if (spEntityIdForSpInitiatedSso == null) {
            throw new NullPointerException("spEntityIdForSpInitiatedSso is marked non-null but is null");
         }

         this.spEntityIdForSpInitiatedSso = spEntityIdForSpInitiatedSso;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder authMethod(@NonNull Provider<AuthenticationMethod> authMethod) {
         if (authMethod == null) {
            throw new NullPointerException("authMethod is marked non-null but is null");
         }

         this.authMethod = authMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder identificationService(
         @NonNull Provider<IdentityProvidingService> identificationService
      ) {
         if (identificationService == null) {
            throw new NullPointerException("identificationService is marked non-null but is null");
         }

         this.identificationService = identificationService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder auditLogger(@NonNull Provider<SigningLogger> auditLogger) {
         if (auditLogger == null) {
            throw new NullPointerException("auditLogger is marked non-null but is null");
         }

         this.auditLogger = auditLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder helpdeskLogger(@NonNull Provider<HelpdeskLogger> helpdeskLogger) {
         if (helpdeskLogger == null) {
            throw new NullPointerException("helpdeskLogger is marked non-null but is null");
         }

         this.helpdeskLogger = helpdeskLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder attributesRepository(
         @NonNull Provider<FlowAttributeRepository> attributesRepository
      ) {
         if (attributesRepository == null) {
            throw new NullPointerException("attributesRepository is marked non-null but is null");
         }

         this.attributesRepository = attributesRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder flowSession(@NonNull Provider<FlowSession> flowSession) {
         if (flowSession == null) {
            throw new NullPointerException("flowSession is marked non-null but is null");
         }

         this.flowSession = flowSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder attributesImporter(
         @NonNull Provider<Saml2AssertionAttributesImporter> attributesImporter
      ) {
         if (attributesImporter == null) {
            throw new NullPointerException("attributesImporter is marked non-null but is null");
         }

         this.attributesImporter = attributesImporter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder tagsFromAttributesService(
         @NonNull Provider<Saml2TagsFromAttributesService> tagsFromAttributesService
      ) {
         if (tagsFromAttributesService == null) {
            throw new NullPointerException("tagsFromAttributesService is marked non-null but is null");
         }

         this.tagsFromAttributesService = tagsFromAttributesService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder federationSessionRepository(
         @NonNull Provider<FlowFederationSessionRepository> federationSessionRepository
      ) {
         if (federationSessionRepository == null) {
            throw new NullPointerException("federationSessionRepository is marked non-null but is null");
         }

         this.federationSessionRepository = federationSessionRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepImpl build() {
         return new Saml2UserIdentifyingStepImpl(
            this.spInitiatedSsoUriFactory,
            this.spInitiatedSsoId,
            this.userSession,
            this.spConfigService,
            this.spEntityIdForSpInitiatedSso,
            this.authMethod,
            this.identificationService,
            this.auditLogger,
            this.helpdeskLogger,
            this.attributesRepository,
            this.flowSession,
            this.attributesImporter,
            this.tagsFromAttributesService,
            this.federationSessionRepository
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2UserIdentifyingStepImpl.Saml2UserIdentifyingStepImplBuilder(spInitiatedSsoUriFactory="
            + this.spInitiatedSsoUriFactory
            + ", spInitiatedSsoId="
            + this.spInitiatedSsoId
            + ", userSession="
            + this.userSession
            + ", spConfigService="
            + this.spConfigService
            + ", spEntityIdForSpInitiatedSso="
            + this.spEntityIdForSpInitiatedSso
            + ", authMethod="
            + this.authMethod
            + ", identificationService="
            + this.identificationService
            + ", auditLogger="
            + this.auditLogger
            + ", helpdeskLogger="
            + this.helpdeskLogger
            + ", attributesRepository="
            + this.attributesRepository
            + ", flowSession="
            + this.flowSession
            + ", attributesImporter="
            + this.attributesImporter
            + ", tagsFromAttributesService="
            + this.tagsFromAttributesService
            + ", federationSessionRepository="
            + this.federationSessionRepository
            + ")";
      }
   }
}
