package com.airlock.iam.oauth2.application.configuration;

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.domain.model.step.acknowledgemessage.AcknowledgeMessageStep;
import com.airlock.iam.flow.shared.domain.model.step.oauth2.OAuth2AuthorizationVerifyingFlowStep;
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 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 = "SSO step using OAuth 2.0. This step starts an OAuth 2.0 flow to identify the user and awards tags to this user.",
   displayName = "OAuth 2.0 SSO Step"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthClient")
@Groups(
   openGroups = {"Basic Settings", "Automated Account Registration"},
   groupSequence = {"Basic Settings", "Automated Account Registration", "Tags/Guards", "Flow Control", "Advanced Settings"}
)
@NextActions(
   {OAuth2AuthorizationVerifyingFlowStep.OAuth2AuthorizationVerifyingFlowStepAction.class, AcknowledgeMessageStep.AcknowledgeMessageStepActions.class}
)
public final class OAuth2SsoStepConfig extends AbstractInteractiveFlowStepConfig implements StepConfigForAuthenticationFlows {
   public static final String OAUTH_SSO_STEP = "OAuth 2.0 SSO Step";
   static final String AUTOMATED_ACCOUNT_REGISTRATION = "Automated Account Registration";
   @PluginProperty(
      displayName = "Provider Identifier",
      description = "The provider id of the flow based client settings to identify the OAuth 2.0 Authorization Server or OpenID Provider.<br>An OAuth 2.0/OIDC Flow Client Settings configuration must be present below the Loginapp referencing the same Provider Identifier.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private OAuth2ProviderIdentifierConfig providerId;
   private static final String PROVIDER_IDENTIFIER_DISPLAY_NAME = "Provider Identifier";
   @StringProperty(
      description = "<p>If no user could be identified, the client will receive this ID as an additional attribute inside the flow response under the key <tt>messageId</tt>.</p> <p>In the Loginapp UI, the ID serves as a translation key. It can be used to display a confirmation page that asks the user whether a new account should be automatically created based on the information received from the OAuth 2.0 provider.</p>",
      displayName = "Message ID",
      optional = true,
      defaultValue = "automated-account-registration-confirmation"
   )
   @GroupProperty(groupIdentifier = "Automated Account Registration")
   private String messageId;
   @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 = "OAUTH2",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;

   private OAuth2SsoStepConfig(
      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,
      OAuth2ProviderIdentifierConfig providerId,
      String messageId,
      String authenticationMethodId
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.providerId = providerId;
      this.messageId = messageId;
      this.authenticationMethodId = authenticationMethodId;
   }

   public OAuth2ProviderIdentifierConfig getProviderId() {
      return this.providerId;
   }

   public String getMessageId() {
      return this.messageId;
   }

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

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2SsoStepConfigBuilder {
      @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 OAuth2ProviderIdentifierConfig providerId;
      @SuppressFBWarnings(justification = "generated code")
      private String messageId;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;

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

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

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

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

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

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

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

         return this;
      }

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoStepConfig.OAuth2SsoStepConfigBuilder providerId(OAuth2ProviderIdentifierConfig providerId) {
         this.providerId = providerId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoStepConfig.OAuth2SsoStepConfigBuilder messageId(String messageId) {
         this.messageId = messageId;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2SsoStepConfig build() {
         return new OAuth2SsoStepConfig(
            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.providerId,
            this.messageId,
            this.authenticationMethodId
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2SsoStepConfig.OAuth2SsoStepConfigBuilder(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
            + ", providerId="
            + this.providerId
            + ", messageId="
            + this.messageId
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ")";
      }
   }
}
