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

import com.airlock.iam.common.application.configuration.credential.SsoTicketExtractorConfig;
import com.airlock.iam.common.application.configuration.sso.SsoTicketContextDataExtractorConfig;
import com.airlock.iam.common.application.configuration.sso.SsoTicketRoleExtractorConfig;
import com.airlock.iam.core.application.configuration.AcceptedSsoTicketRepositoryConfig;
import com.airlock.iam.core.misc.util.ticket.TicketDecoder;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
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.plugin.api.annotation.config.Groups;
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.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;
import lombok.NonNull;

@Plugin(
   description = "Non-interactive SSO ticket flow step. This step extracts an SSO ticket from the request to identify the user and award tags to this user. Up to 100000 tickets are kept in memory (until expiration) to prevent re-use of the same ticket.",
   displayName = "SSO Ticket Authentication Step",
   typeShortName = "SsoTicketAuthenticationStep"
)
@PluginLicenseRestriction(moduleAffiliation = "SSOTickets")
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Tags/Guards", "Flow Control", "Advanced Settings"})
public class SsoTicketIdentifyingStepConfig extends SsoTicketIdentifyingStepConfigBase {
   public static final String SSO_TICKET_AUTHENTICATION_STEP_DISPLAY_NAME = "SSO Ticket Authentication Step";
   @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 = "SSO_TICKET",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;
   @BooleanProperty(
      description = "<p>Ignores ticket data of tickets for the already authenticated user in the session. The step will be successful in such cases. Tickets will in any case still be validated and the step will fail for invalid tickets or tickets identifying a different user than the already authenticated user.</p><p>This configuration might be useful in combination with other features (e.g. OpenId Connect SSO Login Hint), so that the ticket is ignored in case of an existing user session with the same user as in the ticket.</p>",
      displayName = "Ignore Ticket Data Of Tickets For Authenticated User",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean ignoreTicketDataOfTicketsForAuthenticatedUser;

   protected SsoTicketIdentifyingStepConfig(
      FlowConditionConfig skipCondition,
      FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      List<SsoTicketExtractorConfig> ticketExtractors,
      TicketDecoder ticketDecoder,
      @NonNull AcceptedSsoTicketRepositoryConfig acceptedSsoTicketRepository,
      String usernameTicketKey,
      List<SsoTicketTagExtractorConfig> ticketTagExtractors,
      List<SsoTicketRoleExtractorConfig> ticketRoleExtractors,
      List<SsoTicketContextDataExtractorConfig> ticketContextDataExtractors,
      String authenticationMethodId,
      boolean ignoreTicketDataOfTicketsForAuthenticatedUser
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes,
         ticketExtractors,
         ticketDecoder,
         acceptedSsoTicketRepository,
         usernameTicketKey,
         ticketTagExtractors,
         ticketRoleExtractors,
         ticketContextDataExtractors
      );
      if (acceptedSsoTicketRepository == null) {
         throw new NullPointerException("acceptedSsoTicketRepository is marked non-null but is null");
      }

      this.authenticationMethodId = authenticationMethodId;
      this.ignoreTicketDataOfTicketsForAuthenticatedUser = ignoreTicketDataOfTicketsForAuthenticatedUser;
   }

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

   public boolean isIgnoreTicketDataOfTicketsForAuthenticatedUser() {
      return this.ignoreTicketDataOfTicketsForAuthenticatedUser;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class SsoTicketIdentifyingStepConfigBuilder {
      @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 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 List<SsoTicketExtractorConfig> ticketExtractors;
      @SuppressFBWarnings(justification = "generated code")
      private TicketDecoder ticketDecoder;
      @SuppressFBWarnings(justification = "generated code")
      private AcceptedSsoTicketRepositoryConfig acceptedSsoTicketRepository;
      @SuppressFBWarnings(justification = "generated code")
      private String usernameTicketKey;
      @SuppressFBWarnings(justification = "generated code")
      private List<SsoTicketTagExtractorConfig> ticketTagExtractors;
      @SuppressFBWarnings(justification = "generated code")
      private List<SsoTicketRoleExtractorConfig> ticketRoleExtractors;
      @SuppressFBWarnings(justification = "generated code")
      private List<SsoTicketContextDataExtractorConfig> ticketContextDataExtractors;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private boolean ignoreTicketDataOfTicketsForAuthenticatedUser;

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

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

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

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

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

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

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

         return this;
      }

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ticketExtractors(List<SsoTicketExtractorConfig> ticketExtractors) {
         this.ticketExtractors = ticketExtractors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ticketDecoder(TicketDecoder ticketDecoder) {
         this.ticketDecoder = ticketDecoder;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder acceptedSsoTicketRepository(
         @NonNull AcceptedSsoTicketRepositoryConfig acceptedSsoTicketRepository
      ) {
         if (acceptedSsoTicketRepository == null) {
            throw new NullPointerException("acceptedSsoTicketRepository is marked non-null but is null");
         }

         this.acceptedSsoTicketRepository = acceptedSsoTicketRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder usernameTicketKey(String usernameTicketKey) {
         this.usernameTicketKey = usernameTicketKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ticketTagExtractors(List<SsoTicketTagExtractorConfig> ticketTagExtractors) {
         this.ticketTagExtractors = ticketTagExtractors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ticketRoleExtractors(List<SsoTicketRoleExtractorConfig> ticketRoleExtractors) {
         this.ticketRoleExtractors = ticketRoleExtractors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ticketContextDataExtractors(
         List<SsoTicketContextDataExtractorConfig> ticketContextDataExtractors
      ) {
         this.ticketContextDataExtractors = ticketContextDataExtractors;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder ignoreTicketDataOfTicketsForAuthenticatedUser(
         boolean ignoreTicketDataOfTicketsForAuthenticatedUser
      ) {
         this.ignoreTicketDataOfTicketsForAuthenticatedUser = ignoreTicketDataOfTicketsForAuthenticatedUser;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public SsoTicketIdentifyingStepConfig build() {
         return new SsoTicketIdentifyingStepConfig(
            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.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.ticketExtractors,
            this.ticketDecoder,
            this.acceptedSsoTicketRepository,
            this.usernameTicketKey,
            this.ticketTagExtractors,
            this.ticketRoleExtractors,
            this.ticketContextDataExtractors,
            this.authenticationMethodId,
            this.ignoreTicketDataOfTicketsForAuthenticatedUser
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "SsoTicketIdentifyingStepConfig.SsoTicketIdentifyingStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", ticketExtractors="
            + this.ticketExtractors
            + ", ticketDecoder="
            + this.ticketDecoder
            + ", acceptedSsoTicketRepository="
            + this.acceptedSsoTicketRepository
            + ", usernameTicketKey="
            + this.usernameTicketKey
            + ", ticketTagExtractors="
            + this.ticketTagExtractors
            + ", ticketRoleExtractors="
            + this.ticketRoleExtractors
            + ", ticketContextDataExtractors="
            + this.ticketContextDataExtractors
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", ignoreTicketDataOfTicketsForAuthenticatedUser="
            + this.ignoreTicketDataOfTicketsForAuthenticatedUser
            + ")";
      }
   }
}
