package com.airlock.iam.ssi.application.configuration.step;

import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.flow.api.application.configuration.dynamicactivation.DynamicStepActivationConfig;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractInteractiveFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.InteractiveGotoTargetConfig;
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.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.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringListProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.ssi.application.configuration.SsiAttributeConfig;
import com.airlock.iam.ssi.application.configuration.SsiClaimConfig;
import com.airlock.iam.ssi.application.configuration.SsiServiceConfig;
import com.airlock.iam.ssi.domain.model.step.SsiVerificationStep;
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;

@Plugin(
   description = "The SSI passwordless authentication step identifies the user and stores all verified attributes in the flow session, from which they can be read using the 'SSI Verification Data Provider'.<p>The authentication step, passwordless authentication step, and verification step all use the same UI. If translations need to differentiate between them,\n use <a href='https://docs.airlock.com/iam/latest/index/1642782415579.html'>customization with the design kit</a>.</p><p><b>This feature is incubating. Please consult the documentation for more information.</b></p>",
   displayName = "SSI Passwordless Authentication Step"
)
@PluginLicenseRestriction(moduleAffiliation = "AirlockIAM")
@NextActions(SsiVerificationStep.SsiVerificationActions.class)
@Groups(
   openGroups = {"Basic Settings", "Username Claim Settings"},
   groupSequence = {"Basic Settings", "Username Claim Settings", "Tags/Guards", "Flow Control", "Advanced Settings"}
)
public final class SsiPasswordlessAuthenticationStepConfig extends AbstractInteractiveFlowStepConfig implements StepConfigForAuthenticationFlows {
   public static final String SSI_PASSWORDLESS_AUTHENTICATION_STEP_NAME = "SSI Passwordless Authentication Step";
   @PluginProperty(description = "The SSI service to be used by this step.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private SsiServiceConfig service;
   @StringProperty(
      description = "<p>Restricts credential to this proof schema name.</p>\n<p>This schema name must exist on the configured service. The proof schema defines which attributes are requested from the user, see\n<a href='https://docs.procivis.ch/core/proof-schema-management'>Procivis Documentation</a> for more information.</p>",
      displayName = "Proof Schema Name",
      exampleValues = "Airlock Customer",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String proofSchemaName;
   @StringProperty(
      description = "Name of the credential schema from which to extract the username attribute.",
      exampleValues = "Person with Age Check",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Username Claim Settings")
   private String credentialSchemaName;
   @StringListProperty(
      description = "Restricts credential to these issuer DIDs. If left empty, a credential is accepted from any issuer.",
      exampleElements = "did:key:zDnaedCJsgVRW7Ap4p7fa8squDoX5h4SVJ5EjdeQ3pZNw73nQ",
      minElements = 1,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Username Claim Settings")
   private List<String> trustedIssuers;
   @PluginProperty(
      description = "Attribute in the schema which identifies the user. The received attribute value can be transformed to a User ID with the username transformers configured for the authentication flow.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Username Claim Settings")
   private SsiAttributeConfig userNameAttribute;
   @PluginListProperty(
      displayName = "Username Transformation",
      description = "Transforms the username obtained from the verifiable credential. The transformation precedes the flow's Username Transformation. If one of the transformers configured here interrupts the transformation chain, username transformations in the authentication flow's configuration will be skipped.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Username Claim Settings")
   private List<UsernameTransformer> usernameTransformers;
   @PluginListProperty(
      description = "The list of additional claims to be requested from the holder.\nEach item in the list specifies the attributes and predicates to be obtained from a single verifiable credential.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<SsiClaimConfig> additionalClaims;
   @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 = "SSI",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;

   SsiPasswordlessAuthenticationStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      List<InteractiveGotoTargetConfig> interactiveGotoTargets,
      List<DynamicStepActivationConfig> dynamicStepActivations,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      SsiServiceConfig service,
      String proofSchemaName,
      String credentialSchemaName,
      List<String> trustedIssuers,
      SsiAttributeConfig userNameAttribute,
      List<UsernameTransformer> usernameTransformers,
      String authenticationMethodId,
      List<SsiClaimConfig> additionalClaims
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.service = service;
      this.proofSchemaName = proofSchemaName;
      this.credentialSchemaName = credentialSchemaName;
      this.trustedIssuers = trustedIssuers;
      this.userNameAttribute = userNameAttribute;
      this.usernameTransformers = usernameTransformers;
      this.authenticationMethodId = authenticationMethodId;
      this.additionalClaims = additionalClaims;
   }

   public SsiServiceConfig getService() {
      return this.service;
   }

   public String getProofSchemaName() {
      return this.proofSchemaName;
   }

   public String getCredentialSchemaName() {
      return this.credentialSchemaName;
   }

   public List<String> getTrustedIssuers() {
      return this.trustedIssuers;
   }

   public SsiAttributeConfig getUserNameAttribute() {
      return this.userNameAttribute;
   }

   public List<UsernameTransformer> getUsernameTransformers() {
      return this.usernameTransformers;
   }

   public List<SsiClaimConfig> getAdditionalClaims() {
      return this.additionalClaims;
   }

   public String getAuthenticationMethodId() {
      return this.authenticationMethodId;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SsiPasswordlessAuthenticationStepConfigBuilder {
      @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 List<TagConfig> tagsOnSuccess;
      @SuppressFBWarnings(justification = "generated code")
      private StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private List<InteractiveGotoTargetConfig> interactiveGotoTargets;
      @SuppressFBWarnings(justification = "generated code")
      private List<DynamicStepActivationConfig> dynamicStepActivations;
      @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 SsiServiceConfig service;
      @SuppressFBWarnings(justification = "generated code")
      private String proofSchemaName;
      @SuppressFBWarnings(justification = "generated code")
      private String credentialSchemaName;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<String> trustedIssuers;
      @SuppressFBWarnings(justification = "generated code")
      private SsiAttributeConfig userNameAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<UsernameTransformer> usernameTransformers;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<SsiClaimConfig> additionalClaims;

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder tagsOnSuccess(List<TagConfig> tagsOnSuccess) {
         this.tagsOnSuccess = tagsOnSuccess;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder interactiveGotoTargets(
         List<InteractiveGotoTargetConfig> interactiveGotoTargets
      ) {
         this.interactiveGotoTargets = interactiveGotoTargets;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder dynamicStepActivations(
         List<DynamicStepActivationConfig> dynamicStepActivations
      ) {
         this.dynamicStepActivations = dynamicStepActivations;
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder service(SsiServiceConfig service) {
         this.service = service;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder proofSchemaName(String proofSchemaName) {
         this.proofSchemaName = proofSchemaName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder credentialSchemaName(String credentialSchemaName) {
         this.credentialSchemaName = credentialSchemaName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder trustedIssuer(String trustedIssuer) {
         if (this.trustedIssuers == null) {
            this.trustedIssuers = new ArrayList<>();
         }

         this.trustedIssuers.add(trustedIssuer);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder trustedIssuers(Collection<? extends String> trustedIssuers) {
         if (trustedIssuers == null) {
            throw new NullPointerException("trustedIssuers cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder clearTrustedIssuers() {
         if (this.trustedIssuers != null) {
            this.trustedIssuers.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder userNameAttribute(SsiAttributeConfig userNameAttribute) {
         this.userNameAttribute = userNameAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder usernameTransformer(UsernameTransformer usernameTransformer) {
         if (this.usernameTransformers == null) {
            this.usernameTransformers = new ArrayList<>();
         }

         this.usernameTransformers.add(usernameTransformer);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder usernameTransformers(
         Collection<? extends UsernameTransformer> usernameTransformers
      ) {
         if (usernameTransformers == null) {
            throw new NullPointerException("usernameTransformers cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder clearUsernameTransformers() {
         if (this.usernameTransformers != null) {
            this.usernameTransformers.clear();
         }

         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder additionalClaim(SsiClaimConfig additionalClaim) {
         if (this.additionalClaims == null) {
            this.additionalClaims = new ArrayList<>();
         }

         this.additionalClaims.add(additionalClaim);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder additionalClaims(
         Collection<? extends SsiClaimConfig> additionalClaims
      ) {
         if (additionalClaims == null) {
            throw new NullPointerException("additionalClaims cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder clearAdditionalClaims() {
         if (this.additionalClaims != null) {
            this.additionalClaims.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsiPasswordlessAuthenticationStepConfig build() {
         List trustedIssuers = switch (this.trustedIssuers == null ? 0 : this.trustedIssuers.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.trustedIssuers.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.trustedIssuers));
         };

         List usernameTransformers = switch (this.usernameTransformers == null ? 0 : this.usernameTransformers.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.usernameTransformers.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.usernameTransformers));
         };

         return new SsiPasswordlessAuthenticationStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.tagsOnSuccess,
            this.stepId,
            this.interactiveGotoTargets,
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.service,
            this.proofSchemaName,
            this.credentialSchemaName,
            trustedIssuers,
            this.userNameAttribute,
            usernameTransformers,
            this.authenticationMethodId,
            switch (this.additionalClaims == null ? 0 : this.additionalClaims.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.additionalClaims.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.additionalClaims));
            }
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SsiPasswordlessAuthenticationStepConfig.SsiPasswordlessAuthenticationStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", interactiveGotoTargets="
            + this.interactiveGotoTargets
            + ", dynamicStepActivations="
            + this.dynamicStepActivations
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", service="
            + this.service
            + ", proofSchemaName="
            + this.proofSchemaName
            + ", credentialSchemaName="
            + this.credentialSchemaName
            + ", trustedIssuers="
            + this.trustedIssuers
            + ", userNameAttribute="
            + this.userNameAttribute
            + ", usernameTransformers="
            + this.usernameTransformers
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", additionalClaims="
            + this.additionalClaims
            + ")";
      }
   }
}
