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

import com.airlock.iam.authentication.domain.model.password.PasswordAttributeName;
import com.airlock.iam.common.application.configuration.password.repository.PasswordRepositoryConfig;
import com.airlock.iam.core.misc.authen.PasswordPolicy;
import com.airlock.iam.core.misc.impl.authen.NullPasswordPolicy;
import com.airlock.iam.core.misc.plugin.config.validation.CharsetValidator;
import com.airlock.iam.flow.api.application.configuration.redflag.RedFlagConfig;
import com.airlock.iam.flow.api.application.configuration.selection.FlowConditionConfig;
import com.airlock.iam.flow.api.application.configuration.step.AbstractFlowStepConfig;
import com.airlock.iam.flow.api.application.configuration.step.NonInteractiveStepConfig;
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.access.AccessFlowStep;
import com.airlock.iam.flow.types.api.application.configuration.step.StepConfigForAuthenticationFlows;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.lifecycle.AfterConfigActivation;
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 com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

@Plugin(
   description = "<p>An authentication flow step that verifies a username/password combination using HTTP Basic Authentication.</p>\n<p>If there are no preceding interactive steps in the flow, a request to one of the <tt>.../access</tt> endpoints\nwith the HTTP Basic Authentication credentials header will automatically be handled by this step. This allows for completely\nnon-interactive flows that are started with an \"access\" request and are immediately completed successfully, provided the\nHTTP Basic Authentication credentials are valid.</p>",
   displayName = "HTTP Basic Authentication Step"
)
@Groups(openGroups = "Basic Settings", groupSequence = {"Basic Settings", "Advanced Settings", "Flow Control"})
@NextActions(AccessFlowStep.AccessStepActions.class)
public final class HttpBasicAuthenticationStepConfig extends AbstractFlowStepConfig implements StepConfigForAuthenticationFlows, NonInteractiveStepConfig {
   @PluginProperty(description = "The repository of user passwords.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private PasswordRepositoryConfig passwordRepository;
   @StringProperty(
      description = "The \"realm\" defines the protection space of the authentication. Realms allow the protected\nresources on a server to be partitioned into a set of protection spaces,\neach with its own authentication scheme and/or authorization database.",
      defaultValue = "default",
      exampleValues = {"default", "myApplication"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String realm;
   @StringProperty(
      description = "The character set for decoding the basic authentication credentials.",
      defaultValue = "ISO-8859-1",
      suggestedValues = {"ISO-8859-1", "UTF-8"},
      displayName = "Charset",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String charsetName;
   private static final String CHARSET_NAME_FIELD = "charsetName";
   @PluginProperty(
      description = "The password policy that is checked when authenticating. If the policy is violated, a mandatory password change is required.",
      optional = true,
      defaultValue = NullPasswordPolicy.class
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private PasswordPolicy policyToCheckOnLogin;
   @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 = "PASSWORD",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;
   @StringProperty(
      description = "<p>The optional key under which this password will be available in the identity propagation.</p><p>The value can also be retrieved from the session using the \"User Passwords Map\" value map provider.</p><p>If no key is configured, the password cannot be used by identity propagators.</p><p>Important: Multiple Password Authentication steps or Mandatory Password Change steps which have the same value for this property might override each others passwords.<br/>If you have configured a Mandatory Password Change step, you might consider using the same key.</p><p><b>Note:</b> This feature will not work together with end-to-end encryption.</p>",
      optional = true,
      suggestedValues = "PASSWORD"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String passwordAttributeKey;
   @GroupProperty(groupIdentifier = "Tags/Guards")
   @PluginProperty(
      description = "Raises this red flag if a mandatory password change is required. This flag must then be handled by a later step.",
      defaultValue = MandatoryPasswordChangeRedFlagConfig.class,
      optional = true
   )
   private RedFlagConfig passwordChangeRedFlag;
   private Charset charset;

   private HttpBasicAuthenticationStepConfig(
      @Nullable FlowConditionConfig skipCondition,
      @Nullable FlowConditionConfig preCondition,
      boolean requiresActivation,
      List<TagConfig> tagsOnSuccess,
      @Nullable StepIdConfig stepId,
      Map<String, StepIdConfig> onFailureGotos,
      List<ValueMapProviderConfig> customResponseAttributes,
      List<ValueMapProviderConfig> customFailureResponseAttributes,
      PasswordRepositoryConfig passwordRepository,
      PasswordPolicy policyToCheckOnLogin,
      String authenticationMethodId,
      String passwordAttributeKey,
      String realm,
      String charsetName,
      RedFlagConfig passwordChangeRedFlag
   ) {
      super(skipCondition, preCondition, requiresActivation, tagsOnSuccess, stepId, onFailureGotos, customResponseAttributes, customFailureResponseAttributes);
      this.passwordRepository = passwordRepository;
      this.policyToCheckOnLogin = policyToCheckOnLogin;
      this.authenticationMethodId = authenticationMethodId;
      this.passwordAttributeKey = passwordAttributeKey;
      this.realm = realm;
      this.charsetName = charsetName;
      this.passwordChangeRedFlag = passwordChangeRedFlag;
   }

   @ConfigurationValidator
   public static void validateCharset(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      CharsetValidator.validateCharsetProperty("charsetName", config, env, context);
   }

   @AfterConfigActivation
   void setupCharset() {
      this.charset = Charset.forName(this.charsetName);
   }

   public Optional<PasswordAttributeName> getPasswordAttributeName() {
      return Optional.ofNullable(StringUtils.trimToNull(this.passwordAttributeKey)).map(PasswordAttributeName::new);
   }

   public Optional<RedFlagConfig> getPasswordChangeRedFlag() {
      return Optional.ofNullable(this.passwordChangeRedFlag);
   }

   public PasswordRepositoryConfig getPasswordRepository() {
      return this.passwordRepository;
   }

   public String getRealm() {
      return this.realm;
   }

   public String getCharsetName() {
      return this.charsetName;
   }

   public PasswordPolicy getPolicyToCheckOnLogin() {
      return this.policyToCheckOnLogin;
   }

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

   public Charset getCharset() {
      return this.charset;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class HttpBasicAuthenticationStepConfigBuilder {
      @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 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 PasswordRepositoryConfig passwordRepository;
      @SuppressFBWarnings(justification = "generated code")
      private PasswordPolicy policyToCheckOnLogin;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private String passwordAttributeKey;
      @SuppressFBWarnings(justification = "generated code")
      private String realm;
      @SuppressFBWarnings(justification = "generated code")
      private String charsetName;
      @SuppressFBWarnings(justification = "generated code")
      private RedFlagConfig passwordChangeRedFlag;

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

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder skipCondition(@Nullable FlowConditionConfig skipCondition) {
         this.skipCondition = skipCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder preCondition(@Nullable FlowConditionConfig preCondition) {
         this.preCondition = preCondition;
         return this;
      }

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder passwordRepository(PasswordRepositoryConfig passwordRepository) {
         this.passwordRepository = passwordRepository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder policyToCheckOnLogin(PasswordPolicy policyToCheckOnLogin) {
         this.policyToCheckOnLogin = policyToCheckOnLogin;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder passwordAttributeKey(String passwordAttributeKey) {
         this.passwordAttributeKey = passwordAttributeKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder realm(String realm) {
         this.realm = realm;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder charsetName(String charsetName) {
         this.charsetName = charsetName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder passwordChangeRedFlag(RedFlagConfig passwordChangeRedFlag) {
         this.passwordChangeRedFlag = passwordChangeRedFlag;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HttpBasicAuthenticationStepConfig build() {
         return new HttpBasicAuthenticationStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.tagsOnSuccess,
            this.stepId,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.passwordRepository,
            this.policyToCheckOnLogin,
            this.authenticationMethodId,
            this.passwordAttributeKey,
            this.realm,
            this.charsetName,
            this.passwordChangeRedFlag
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "HttpBasicAuthenticationStepConfig.HttpBasicAuthenticationStepConfigBuilder(skipCondition="
            + this.skipCondition
            + ", preCondition="
            + this.preCondition
            + ", requiresActivation="
            + this.requiresActivation
            + ", tagsOnSuccess="
            + this.tagsOnSuccess
            + ", stepId="
            + this.stepId
            + ", onFailureGotos="
            + this.onFailureGotos
            + ", customResponseAttributes="
            + this.customResponseAttributes
            + ", customFailureResponseAttributes="
            + this.customFailureResponseAttributes
            + ", passwordRepository="
            + this.passwordRepository
            + ", policyToCheckOnLogin="
            + this.policyToCheckOnLogin
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", passwordAttributeKey="
            + this.passwordAttributeKey
            + ", realm="
            + this.realm
            + ", charsetName="
            + this.charsetName
            + ", passwordChangeRedFlag="
            + this.passwordChangeRedFlag
            + ")";
      }
   }
}
