package com.airlock.iam.authentication.application.configuration.devicetoken;

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.shared.application.configuration.devicetoken.DeviceTokenSettings;
import com.airlock.iam.flow.shared.domain.model.devicetoken.DeviceTokenVerificationStep;
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.BooleanProperty;
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 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 = "Configuration for a device token flow step.", displayName = "Device Token Authentication Step")
@PluginLicenseRestriction(moduleAffiliation = "DeviceToken")
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control", "Advanced Settings"})
@NextActions(DeviceTokenVerificationStep.DeviceTokenVerificationStepActions.class)
public class DeviceTokenAuthStepConfig extends AbstractInteractiveFlowStepConfig implements StepConfigForAuthenticationFlows {
   @PluginProperty(description = "Device token settings used for this step.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private DeviceTokenSettings deviceTokenSettings;
   @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 = "DEVICE_TOKEN",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;
   @BooleanProperty(
      description = "Always fail during challenge retrieval if the device token does not exist for the provided ID. This behavior can be used to e.g. test if a device token exists on the IAM while the user is not yet identified. Failures during challenge retrieve with unidentified users will not increase failed login attempts. This will break the device ID enumeration protection property of this step.",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean failOnChallengeRetrieve;

   protected DeviceTokenAuthStepConfig(
      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,
      DeviceTokenSettings deviceTokenSettings,
      String authenticationMethodId,
      boolean failOnChallengeRetrieve
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.deviceTokenSettings = deviceTokenSettings;
      this.authenticationMethodId = authenticationMethodId;
      this.failOnChallengeRetrieve = failOnChallengeRetrieve;
   }

   public DeviceTokenSettings getDeviceTokenSettings() {
      return this.deviceTokenSettings;
   }

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

   public boolean isFailOnChallengeRetrieve() {
      return this.failOnChallengeRetrieve;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class DeviceTokenAuthStepConfigBuilder {
      @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 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 DeviceTokenSettings deviceTokenSettings;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private boolean failOnChallengeRetrieve;

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public DeviceTokenAuthStepConfig.DeviceTokenAuthStepConfigBuilder 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 DeviceTokenAuthStepConfig.DeviceTokenAuthStepConfigBuilder clearTagsOnSuccess() {
         if (this.tagsOnSuccess != null) {
            this.tagsOnSuccess.clear();
         }

         return this;
      }

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public DeviceTokenAuthStepConfig.DeviceTokenAuthStepConfigBuilder deviceTokenSettings(DeviceTokenSettings deviceTokenSettings) {
         this.deviceTokenSettings = deviceTokenSettings;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public DeviceTokenAuthStepConfig.DeviceTokenAuthStepConfigBuilder failOnChallengeRetrieve(boolean failOnChallengeRetrieve) {
         this.failOnChallengeRetrieve = failOnChallengeRetrieve;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DeviceTokenAuthStepConfig build() {
         return new DeviceTokenAuthStepConfig(
            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.interactiveGotoTargets,
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.deviceTokenSettings,
            this.authenticationMethodId,
            this.failOnChallengeRetrieve
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "DeviceTokenAuthStepConfig.DeviceTokenAuthStepConfigBuilder(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
            + ", deviceTokenSettings="
            + this.deviceTokenSettings
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", failOnChallengeRetrieve="
            + this.failOnChallengeRetrieve
            + ")";
      }
   }
}
