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

import com.airlock.iam.common.api.domain.service.email.EmailService;
import com.airlock.iam.common.application.configuration.masking.MaskingConfig;
import com.airlock.iam.core.misc.authen.SecretStringGenerator;
import com.airlock.iam.core.misc.impl.authen.ExtendedStringGenerator;
import com.airlock.iam.core.misc.persistency.CredentialPersister;
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.otp.GenericOtpCheckingStep;
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.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
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 com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
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 java.util.Optional;
import org.apache.commons.lang3.Strings;

@Plugin(
   description = "<p>Configuration for an authentication flow step to check an OTP sent via email.</p><b>Note:</b> Emails are neither confidential nor authentic (i.e. the user cannot be sure that the email is really from Airlock IAM and Airlock IAM cannot be sure that the email is delivered to the correct user). Therefore, this step must not be used for high-security applications.</p>",
   displayName = "Legacy Email OTP Authentication Step"
)
@Groups(groupSequence = {"Basic Settings", "Security Settings", "Tags/Guards", "Flow Control", "Advanced Settings"}, openGroups = "Basic Settings")
@NextActions(GenericOtpCheckingStep.GenericOtpStepActions.class)
public final class LegacyEmailOtpAuthenticationStepConfig extends AbstractInteractiveFlowStepConfig implements StepConfigForAuthenticationFlows {
   @PluginProperty(
      description = "Email service plugin. This defines what mail server is used for sending the email. It also defines the sender address and whether the email should be signed or not.",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private EmailService emailService;
   @StringProperty(
      description = "Message template used to create the message text sent to the user. <p>The string $TOKEN$ in the message template is mandatory and is replaced by the generated OTP.</p><p>If the message text is in HTML code, enable the property \"Message Template Is HTML\".</p>",
      optional = true,
      isMultiLineText = true,
      exampleValues = "<b>Authentication Code</b><p>In order to access our services, please provide the following security code: <code>$TOKEN$</code></p><b>Best Regards,<br/>Your Airlock IAM Server</b>",
      defaultValue = "$TOKEN$"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String messageTemplate;
   private static final String MESSAGE_TEMPLATE_FIELD = "messageTemplate";
   @BooleanProperty(
      description = "Enable if the message template is HTML code.",
      optional = true,
      defaultValue = "true",
      displayName = "Message Template Is HTML"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean messageTemplateIsHtml;
   @StringProperty(description = "The subject used in the email.", optional = false, exampleValues = "Security Code for Login")
   @GroupProperty(groupIdentifier = "Basic Settings")
   private String emailSubject;
   @BooleanProperty(description = "If enabled, the case of characters is ignored when checking OTPs.", defaultValue = "false", optional = true)
   @GroupProperty(groupIdentifier = "Security Settings")
   private boolean ignoreCase;
   @PluginProperty(description = "Credential persister to load the email address of the user.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private CredentialPersister credentialPersister;
   @PluginProperty(
      description = "Settings for masking the email address in the REST API responses. Please refer to the REST API documentation for further details.<p>If left empty, the email address will not be masked.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private MaskingConfig maskingSettings;
   @PluginProperty(
      description = "The string generator plugin to generate the OTP.",
      optional = true,
      defaultValue = ExtendedStringGenerator.class,
      displayName = "OTP Generator"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private SecretStringGenerator otpGenerator;
   @IntegerProperty(
      description = "The number of seconds for which an OTP is valid. If the OTP is entered correctly but after its expiration, authentication will fail. (TOKEN_EXPIRED).<p>The value 0 (zero) disables this feature, i.e. OTPs never expire (this is the default).</p>",
      defaultValue = 0,
      minValue = 0,
      optional = true,
      exampleValues = {0, 30, 120},
      displayName = "OTP Validity [seconds]"
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private int otpValiditySeconds;
   @IntegerProperty(
      description = "The number of times the user may enter a wrong OTP before the authentication process is aborted. If set to zero (the default), only one attempt is possible. This is more secure but less user-friendly.",
      minValue = 0,
      defaultValue = 0,
      optional = true,
      exampleValues = {0, 3, 5}
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private int maxRetries;
   @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 = "EMAIL",
      maxLength = 23,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String authenticationMethodId;

   public Optional<MaskingConfig> getMaskingSettings() {
      return Optional.ofNullable(this.maskingSettings);
   }

   @ConfigurationValidator
   public static void assureTokenReplacementInMessageTemplate(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv
   ) throws ConfigurationException {
      String messageTemplate = StringPropertyValueReader.effectiveStringConfigValue(config, "messageTemplate", env, context);
      if (!Strings.CS.contains(messageTemplate, "$TOKEN$")) {
         throw new PropertyConstraintViolationException(
            "messageTemplate", config, env, context, "The email message template does not contain the token replacement \"$TOKEN$\"."
         );
      }
   }

   private LegacyEmailOtpAuthenticationStepConfig(
      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,
      EmailService emailService,
      String messageTemplate,
      boolean messageTemplateIsHtml,
      String emailSubject,
      boolean ignoreCase,
      CredentialPersister credentialPersister,
      int otpValiditySeconds,
      int maxRetries,
      SecretStringGenerator otpGenerator,
      String authenticationMethodId,
      MaskingConfig maskingSettings
   ) {
      super(
         skipCondition,
         preCondition,
         requiresActivation,
         tagsOnSuccess,
         stepId,
         interactiveGotoTargets,
         dynamicStepActivations,
         onFailureGotos,
         customResponseAttributes,
         customFailureResponseAttributes
      );
      this.emailService = emailService;
      this.messageTemplate = messageTemplate;
      this.messageTemplateIsHtml = messageTemplateIsHtml;
      this.emailSubject = emailSubject;
      this.ignoreCase = ignoreCase;
      this.credentialPersister = credentialPersister;
      this.otpValiditySeconds = otpValiditySeconds;
      this.maxRetries = maxRetries;
      this.otpGenerator = otpGenerator;
      this.authenticationMethodId = authenticationMethodId;
      this.maskingSettings = maskingSettings;
   }

   public EmailService getEmailService() {
      return this.emailService;
   }

   public String getMessageTemplate() {
      return this.messageTemplate;
   }

   public boolean isMessageTemplateIsHtml() {
      return this.messageTemplateIsHtml;
   }

   public String getEmailSubject() {
      return this.emailSubject;
   }

   public boolean isIgnoreCase() {
      return this.ignoreCase;
   }

   public CredentialPersister getCredentialPersister() {
      return this.credentialPersister;
   }

   public SecretStringGenerator getOtpGenerator() {
      return this.otpGenerator;
   }

   public int getOtpValiditySeconds() {
      return this.otpValiditySeconds;
   }

   public int getMaxRetries() {
      return this.maxRetries;
   }

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

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class LegacyEmailOtpAuthenticationStepConfigBuilder {
      @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 ArrayList<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 EmailService emailService;
      @SuppressFBWarnings(justification = "generated code")
      private String messageTemplate;
      @SuppressFBWarnings(justification = "generated code")
      private boolean messageTemplateIsHtml;
      @SuppressFBWarnings(justification = "generated code")
      private String emailSubject;
      @SuppressFBWarnings(justification = "generated code")
      private boolean ignoreCase;
      @SuppressFBWarnings(justification = "generated code")
      private CredentialPersister credentialPersister;
      @SuppressFBWarnings(justification = "generated code")
      private int otpValiditySeconds;
      @SuppressFBWarnings(justification = "generated code")
      private int maxRetries;
      @SuppressFBWarnings(justification = "generated code")
      private SecretStringGenerator otpGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private String authenticationMethodId;
      @SuppressFBWarnings(justification = "generated code")
      private MaskingConfig maskingSettings;

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

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

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder 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 LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder clearInteractiveGotoTargets() {
         if (this.interactiveGotoTargets != null) {
            this.interactiveGotoTargets.clear();
         }

         return this;
      }

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder emailService(EmailService emailService) {
         this.emailService = emailService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder messageTemplate(String messageTemplate) {
         this.messageTemplate = messageTemplate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder messageTemplateIsHtml(boolean messageTemplateIsHtml) {
         this.messageTemplateIsHtml = messageTemplateIsHtml;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder emailSubject(String emailSubject) {
         this.emailSubject = emailSubject;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder ignoreCase(boolean ignoreCase) {
         this.ignoreCase = ignoreCase;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder credentialPersister(CredentialPersister credentialPersister) {
         this.credentialPersister = credentialPersister;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder otpValiditySeconds(int otpValiditySeconds) {
         this.otpValiditySeconds = otpValiditySeconds;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder maxRetries(int maxRetries) {
         this.maxRetries = maxRetries;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder otpGenerator(SecretStringGenerator otpGenerator) {
         this.otpGenerator = otpGenerator;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder maskingSettings(MaskingConfig maskingSettings) {
         this.maskingSettings = maskingSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LegacyEmailOtpAuthenticationStepConfig build() {
         return new LegacyEmailOtpAuthenticationStepConfig(
            this.skipCondition,
            this.preCondition,
            this.requiresActivation,
            this.tagsOnSuccess,
            this.stepId,
            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));
            },
            this.dynamicStepActivations,
            this.onFailureGotos,
            this.customResponseAttributes,
            this.customFailureResponseAttributes,
            this.emailService,
            this.messageTemplate,
            this.messageTemplateIsHtml,
            this.emailSubject,
            this.ignoreCase,
            this.credentialPersister,
            this.otpValiditySeconds,
            this.maxRetries,
            this.otpGenerator,
            this.authenticationMethodId,
            this.maskingSettings
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "LegacyEmailOtpAuthenticationStepConfig.LegacyEmailOtpAuthenticationStepConfigBuilder(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
            + ", emailService="
            + this.emailService
            + ", messageTemplate="
            + this.messageTemplate
            + ", messageTemplateIsHtml="
            + this.messageTemplateIsHtml
            + ", emailSubject="
            + this.emailSubject
            + ", ignoreCase="
            + this.ignoreCase
            + ", credentialPersister="
            + this.credentialPersister
            + ", otpValiditySeconds="
            + this.otpValiditySeconds
            + ", maxRetries="
            + this.maxRetries
            + ", otpGenerator="
            + this.otpGenerator
            + ", authenticationMethodId="
            + this.authenticationMethodId
            + ", maskingSettings="
            + this.maskingSettings
            + ")";
      }
   }
}
