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

import com.airlock.iam.authentication.domain.model.identification.UserIdentifyingFidoStep;
import com.airlock.iam.factor.application.configuration.fido.FidoSettingsConfig;
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.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.step.CommonFidoCheckingStepConfig;
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.PluginMapProperty;
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 = "FIDO")
@Plugin(
   description = "<p>User identifying flow step with integrated FIDO authentication.</p>\n<p>This step identifies users only by their username, thus it should only be integrated into flows where\neither a trusted, authenticated entity acts on behalf of the user, or further authentication steps\nverify the user's identity.</p>\n<p>Additionally, this step directly integrates FIDO passwordless authentication by supporting Webauthn\nconditional UI. When accessing the page, the user is automatically presented with FIDO credential selection\ndialog if it is supported by the browser, and allows the user to authenticate directly.</p>\n<p><b>Caution:</b> This step allows user enumeration attacks. Such attacks cannot be prevented,\neven if \"Prevent User-Enumeration\" is enabled on the \"Authentication Flow\".</p>",
   displayName = "User Identification with FIDO Authentication Step"
)
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control"})
@NextActions(UserIdentifyingFidoStep.UserIdentifyingFidoStepAction.class)
public final class UserIdentifyingFidoStepConfig extends CommonFidoCheckingStepConfig implements StepConfigForAuthenticationFlows {
   public static final String USER_FIDO_IDENTIFYING_STEP_PLUGIN_NAME = "User Identification with FIDO Authentication Step";
   @PluginListProperty(
      displayName = "Tags on User Identify Success",
      description = "This step grants these tags if and only if the user identification was successful.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Tags/Guards")
   private List<TagConfig> tagsOnUserIdentifySuccess;
   @PluginListProperty(
      displayName = "Tags on FIDO Auth Success",
      description = "This step grants these tags if and only if the FIDO authentication was completed successfully.",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Tags/Guards")
   private List<TagConfig> tagsOnFidoAuthSuccess;
   @PluginMapProperty(
      description = "<p> If the step fails (no retry) and a goto target for the error code is defined here, the flow does not fail\nand instead a \"goto\" to the specified target step is executed. Note that even when the \"goto\" is executed, any\nerror codes that are considered a failed factor attempt will still increment the \"failed attempts\" counter,\nand may lead to the user being locked. Therefore, this may still result in a failed flow.</p>\n<p>A typical application of this feature is switching to an alternative authentication factor step, if an external\nservice (e.g. Futurae server, SMS gateway) is not available (error code <code>EXTERNAL_SERVICE_UNAVAILABLE</code>\nwith \"Strict Counting\" disabled, which will not increment the \"failed attempts\" counter).\nOther error codes can be found in the IAM REST documentation, in both the general \"Error Codes\" section and\nin the documentation of specific endpoints.</p>\n<p>This step can return the following error codes:\n<ul>\n  <li><code>USER_NOT_FOUND</code>: The user with the specified identity cannot be found.</li>\n  <li><code>FIDO_AUTHENTICATION_FAILED</code>: The FIDO authentication failed for unspecified reasons (either in the browser/client or during server-side verification).</li>\n  <li><code>FIDO_AUTHENTICATION_TIMEOUT</code>: The response from the browser/client has timed out.</li>\n  <li><code>FIDO_AUTHENTICATION_ABORTED</code>: The authentication has been aborted in the browser/client.</li>\n  <li><code>FIDO_AUTHENTICATION_NOT_ALLOWED</code>: The browser/client did not allow authentication with the given credentials.</li>\n  <li><code>FIDO_WEB_AUTHN_NOT_AVAILABLE</code>: The client/browser is not capable of performing WebAuthn/FIDO authentication.</li>\n</ul></p>\n",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Flow Control")
   private Map<String, StepIdConfig> onFailureGotos;
   private List<TagConfig> tagsOnSuccess;

   private UserIdentifyingFidoStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      List<TagConfig> tagsOnUserIdentifySuccess,
      List<TagConfig> tagsOnFidoAuthSuccess,
      @Nullable StepIdConfig stepId,
      List<InteractiveGotoTargetConfig> interactiveGotoTargets,
      List<DynamicStepActivationConfig> dynamicStepActivations,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      FidoSettingsConfig fidoSettings
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         fidoSettings,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.tagsOnUserIdentifySuccess = tagsOnUserIdentifySuccess;
      this.tagsOnFidoAuthSuccess = tagsOnFidoAuthSuccess;
      this.onFailureGotos = onFailureGotos;
   }

   @Override
   public Map<String, StepIdConfig> getOnFailureGotos() {
      return this.onFailureGotos;
   }

   public List<TagConfig> getTagsOnFidoAuthSuccess() {
      return this.tagsOnFidoAuthSuccess;
   }

   public List<TagConfig> getTagsOnUserIdentifySuccess() {
      return this.tagsOnUserIdentifySuccess;
   }

   @Override
   public List<TagConfig> getTagsOnSuccess() {
      return List.of();
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class UserIdentifyingFidoStepConfigBuilder {
      @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 ArrayList<TagConfig> tagsOnUserIdentifySuccess;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<TagConfig> tagsOnFidoAuthSuccess;
      @SuppressFBWarnings(justification = "generated code")
      private StepIdConfig stepId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<InteractiveGotoTargetConfig> interactiveGotoTargets;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<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 FidoSettingsConfig fidoSettings;

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

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

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

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

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

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

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

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder tagsOnUserIdentifySuccess(TagConfig tagsOnUserIdentifySuccess) {
         if (this.tagsOnUserIdentifySuccess == null) {
            this.tagsOnUserIdentifySuccess = new ArrayList<>();
         }

         this.tagsOnUserIdentifySuccess.add(tagsOnUserIdentifySuccess);
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder clearTagsOnUserIdentifySuccess() {
         if (this.tagsOnUserIdentifySuccess != null) {
            this.tagsOnUserIdentifySuccess.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder tagsOnFidoAuthSuccess(TagConfig tagsOnFidoAuthSuccess) {
         if (this.tagsOnFidoAuthSuccess == null) {
            this.tagsOnFidoAuthSuccess = new ArrayList<>();
         }

         this.tagsOnFidoAuthSuccess.add(tagsOnFidoAuthSuccess);
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder clearTagsOnFidoAuthSuccess() {
         if (this.tagsOnFidoAuthSuccess != null) {
            this.tagsOnFidoAuthSuccess.clear();
         }

         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder interactiveGotoTarget(InteractiveGotoTargetConfig interactiveGotoTarget) {
         if (this.interactiveGotoTargets == null) {
            this.interactiveGotoTargets = new ArrayList<>();
         }

         this.interactiveGotoTargets.add(interactiveGotoTarget);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder interactiveGotoTargets(
         Collection<? extends InteractiveGotoTargetConfig> interactiveGotoTargets
      ) {
         if (interactiveGotoTargets == null) {
            throw new NullPointerException("interactiveGotoTargets cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder clearInteractiveGotoTargets() {
         if (this.interactiveGotoTargets != null) {
            this.interactiveGotoTargets.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder dynamicStepActivation(DynamicStepActivationConfig dynamicStepActivation) {
         if (this.dynamicStepActivations == null) {
            this.dynamicStepActivations = new ArrayList<>();
         }

         this.dynamicStepActivations.add(dynamicStepActivation);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder dynamicStepActivations(
         Collection<? extends DynamicStepActivationConfig> dynamicStepActivations
      ) {
         if (dynamicStepActivations == null) {
            throw new NullPointerException("dynamicStepActivations cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder clearDynamicStepActivations() {
         if (this.dynamicStepActivations != null) {
            this.dynamicStepActivations.clear();
         }

         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder fidoSettings(FidoSettingsConfig fidoSettings) {
         this.fidoSettings = fidoSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserIdentifyingFidoStepConfig build() {
         List tagsOnSuccess = 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));
         };

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

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

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

         return new UserIdentifyingFidoStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            tagsOnSuccess,
            tagsOnUserIdentifySuccess,
            tagsOnFidoAuthSuccess,
            this.stepId,
            interactiveGotoTargets,
            switch (this.dynamicStepActivations == null ? 0 : this.dynamicStepActivations.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.dynamicStepActivations.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.dynamicStepActivations));
            },
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.fidoSettings
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "UserIdentifyingFidoStepConfig.UserIdentifyingFidoStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", tagsOnUserIdentifySuccess="
            + this.tagsOnUserIdentifySuccess
            + ", tagsOnFidoAuthSuccess="
            + this.tagsOnFidoAuthSuccess
            + ", stepId="
            + this.stepId
            + ", interactiveGotoTargets="
            + this.interactiveGotoTargets
            + ", dynamicStepActivations="
            + this.dynamicStepActivations
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", fidoSettings="
            + this.fidoSettings
            + ")";
      }
   }
}
