package com.airlock.iam.saml2.application.configuration.sp;

import com.airlock.iam.common.api.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.core.misc.impl.authen.AuthenticationMethodFactory;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.NonInteractiveStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.StepIdConfig;
import com.airlock.iam.flow.api.application.configuration.tag.TagConfig;
import com.airlock.iam.flow.api.application.configuration.valueprovider.ValueMapProviderConfig;
import com.airlock.iam.flow.api.domain.model.step.GenericStepAction;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForAuthenticationFlows;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.NextActions;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.saml2.application.configuration.Saml2ServiceProviderIdConfig;
import com.airlock.iam.saml2.domain.model.sp.Saml2SpEntityId;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@PluginLicenseRestriction(moduleAffiliation = "SamlSp")
@Plugin(
   description = "<p>User identifying step for SAML 2.0 single sign-on (SSO) on IAM as service provider (SP).</p>\n<p>If the SAML 2.0 handshake was already initiated by the identity provider (IdP), this step consumes the assertion.\nIf no handshake took place yet, this step first initiates an SP-Initiated SSO (and redirects to the IdP) and when returning consumes the assertion.</p>\n",
   displayName = "SAML 2.0 SP User Identifying Step"
)
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control"})
@NextActions(GenericStepAction.class)
public final class Saml2UserIdentifyingStepConfig extends AbstractFlowStepConfig implements StepConfigForAuthenticationFlows, NonInteractiveStepConfig {
   @PluginProperty(
      description = "Configures the Unique ID of the Service Provider (SP) used for SP-initiated SSO. In case of SSO initiated by the identity provider (IdP-initiated SSO), the SP is determined by the SAML 2.0 assertion sent by the IdP.",
      optional = false,
      displayName = "SP Entity ID"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private Saml2ServiceProviderIdConfig entityId;
   public static final String SERVICE_PROVIDER_ID_PROPERTY = "entityId";
   @StringProperty(
      description = "The identifier of the authentication method for this step. Since the authentication method is also the identifier for failed login attempts for this step, distinct identifiers must be chosen if multiple instances of the same step type are used to check different credentials (e.g., two password step instances check two different passwords).<p>Example: a system tracks two passwords per user. Password 1 is used in flow A, password 2 in flow B. These two steps must have distinct 'Authentication Method Ids', e.g. PASSWORD1 and PASSWORD2.</p>If only one identifier (e.g. \"PASSWORD\") is used, this may allow brute force attacks on the password as follows. Assuming an attacker knows the user's password 1, they get an unlimited number of attempts on flow B, as the 'PASSWORD' counter can repeatedly be reset to 0 by performing a successful login on flow A.<p>To prevent such attacks, use two different counters by setting the authentication methods, for example, to PASSWORD1 and PASSWORD2, respectively.</p>",
      displayName = "Authentication Method ID",
      defaultValue = "SAML2",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;

   private Saml2UserIdentifyingStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      Saml2ServiceProviderIdConfig entityId,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      String authenticationMethodId
   ) {
      super(skipCondition, preCondition, requiresActivation, tagsOnSuccess, stepId, onFailureGotos, customResponseAttributes, customFailureResponseAttributes);
      this.entityId = entityId;
      this.authenticationMethodId = authenticationMethodId;
   }

   public Saml2SpEntityId getSaml2SpEntityId() {
      return Saml2SpEntityId.spEntityId(this.entityId.getSpEntityId());
   }

   public AuthenticationMethod getAuthenticationMethod() {
      return AuthenticationMethodFactory.authMethodFor(this.authenticationMethodId);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public Saml2UserIdentifyingStepConfig() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2UserIdentifyingStepConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig skipCondition;
      @SuppressFBWarnings(justification = "generated code")
      private FlowConditionConfig preCondition;
      @SuppressFBWarnings(justification = "generated code")
      private boolean requiresActivation;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<TagConfig> tagsOnSuccess;
      @SuppressFBWarnings(justification = "generated code")
      private StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private Saml2ServiceProviderIdConfig entityId;
      @SuppressFBWarnings(justification = "generated code")
      private Map<String, StepIdConfig> onFailureGotos;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private List<ValueMapProviderConfig> customFailureResponseAttributes;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;

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

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder skipCondition(FlowConditionConfig skipCondition) {
         this.skipCondition = skipCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder preCondition(FlowConditionConfig preCondition) {
         this.preCondition = preCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder requiresActivation(boolean requiresActivation) {
         this.requiresActivation = requiresActivation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder tagOnSuccess(TagConfig tagOnSuccess) {
         if (this.tagsOnSuccess == null) {
            this.tagsOnSuccess = new ArrayList<>();
         }

         this.tagsOnSuccess.add(tagOnSuccess);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder tagsOnSuccess(Collection<? extends TagConfig> tagsOnSuccess) {
         if (tagsOnSuccess == null) {
            throw new NullPointerException("tagsOnSuccess cannot be null");
         }

         if (this.tagsOnSuccess == null) {
            this.tagsOnSuccess = new ArrayList<>();
         }

         this.tagsOnSuccess.addAll(tagsOnSuccess);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder clearTagsOnSuccess() {
         if (this.tagsOnSuccess != null) {
            this.tagsOnSuccess.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder stepId(@Nullable StepIdConfig stepId) {
         this.stepId = stepId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder entityId(Saml2ServiceProviderIdConfig entityId) {
         this.entityId = entityId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder onFailureGotos(Map<String, StepIdConfig> onFailureGotos) {
         this.onFailureGotos = onFailureGotos;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder customResponseAttributes(
         List<ValueMapProviderConfig> customResponseAttributes
      ) {
         this.customResponseAttributes = customResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder customFailureResponseAttributes(
         List<ValueMapProviderConfig> customFailureResponseAttributes
      ) {
         this.customFailureResponseAttributes = customFailureResponseAttributes;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder authenticationMethodId(String authenticationMethodId) {
         this.authenticationMethodId = authenticationMethodId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingStepConfig build() {
         return new Saml2UserIdentifyingStepConfig(
            this.skipCondition, this.preCondition, this.requiresActivation, switch (this.tagsOnSuccess == null ? 0 : this.tagsOnSuccess.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.tagsOnSuccess.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.tagsOnSuccess));
            }, this.stepId, this.entityId, this.onFailureGotos, this.customResponseAttributes, this.customFailureResponseAttributes, this.authenticationMethodId
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2UserIdentifyingStepConfig.Saml2UserIdentifyingStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", entityId="
            + this.entityId
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ")";
      }
   }
}
