package com.airlock.iam.flow.shared.application.configuration.item;

import com.airlock.iam.common.api.application.configuration.validation.StringValidatorConfig;
import com.airlock.iam.common.api.application.configuration.valueprovider.StringValueProviderConfig;
import com.airlock.iam.common.application.configuration.validation.EmailAddressValidatorConfig;
import com.airlock.iam.flow.shared.api.application.configuration.item.SelfServiceItemDefinitionConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
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.PluginListPropertyValueReader;
import com.airlock.iam.plugin.framework.application.service.builder.plugin.PluginPropertyBuilderInitializer;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Plugin(
   description = "Item to register an email address. By default, the email address is validated by the \"Email Address Validator\" plugin but additional configurable validators can further restrict the allowed input values. Once validated, the email address is kept in the session to be verified and persisted later in the flow.",
   displayName = "Email Item Definition"
)
public class EmailItemDefinitionConfig implements SelfServiceItemDefinitionConfig {
   public static final String EMAIL_ITEM_DEFINITION_DISPLAY_NAME = "Email Item Definition";
   @StringProperty(description = "The key under which the client is expected to provide the email address.", defaultValue = "email", optional = true)
   private String key;
   public static final String KEY_PROPERTY = "key";
   @BooleanProperty(description = "Specifies whether this item is required for the step to validate successfully.", defaultValue = "true", optional = true)
   private boolean required;
   @BooleanProperty(description = "Specifies whether the email address must be unique across all users.", defaultValue = "true", optional = true)
   private boolean unique;
   @BooleanProperty(
      description = "Enables stealth mode uniqueness validation. In stealth mode, uniqueness violations are not communicated to the client. Instead, the email verification step will simulate sending the email OTP and all submitted OTPs will be invalid.",
      defaultValue = "true",
      optional = true
   )
   private boolean enableStealthMode;
   @IntegerProperty(
      description = "Maximum length of email addresses. 100 characters is the default IAM email database field limit. IETF accepted errata against RFC 3696 allow up to 254 characters.",
      optional = true,
      defaultValue = 100,
      maxValue = 254
   )
   private int maximumInputLength;
   @PluginProperty(description = "Specifies how to retrieve the user's initial email address.", optional = true)
   private StringValueProviderConfig initialValueProvider;
   @StringProperty(
      description = "<p>The input purpose allows labeling data items using standardized values\n(see <a href=\"https://www.w3.org/TR/WCAG22/#input-purposes\" target=\"_blank\">https://www.w3.org/TR/WCAG22/#input-purposes</a>).</p>\n<p>It is rendered using the HTML attribute \"autocomplete\".\nBrowsers can use this to automatically fill input fields with data that was previously entered in other fields with the same purpose.</p><p>Note that the input purpose provided here will be used in the default Loginapp UI components and is available to\ncustom single-page applications via the REST endpoints <code>*/info/retrieve</code>.</p>\n<p>If the Loginapp UI is used with <em>configuration-based</em> 'Customized Step UIs', the input purpose has to be defined on the UI elements\n('Input UI Element', 'Drop-Down UI Element', 'Date UI Element').</p>",
      optional = true,
      suggestedValues = {"email", "username"}
   )
   private String inputPurpose;
   @PluginListProperty(
      description = "Validators for the email address. E.g. to restrict the email address to certain domains. <p>The \"Email Address Validator\" is automatically applied. A manual configuration is not necessary.</p>",
      optional = true
   )
   private List<StringValidatorConfig> validators;
   private static final String VALIDATORS_PROPERTY = "validators";
   private List<StringValidatorConfig> initializedValidators;

   public EmailItemDefinitionConfig(
      String key,
      boolean required,
      boolean unique,
      boolean enableStealthMode,
      int maximumInputLength,
      StringValueProviderConfig initialValueProvider,
      String inputPurpose,
      List<StringValidatorConfig> validators
   ) {
      this.key = key;
      this.required = required;
      this.unique = unique;
      this.enableStealthMode = enableStealthMode;
      this.maximumInputLength = maximumInputLength;
      this.initialValueProvider = initialValueProvider;
      this.inputPurpose = inputPurpose;
      this.validators = validators;
   }

   @ConfigurationValidator
   static void checkEmailValidatorNotManuallyConfigured(
      PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      for (PluginConfiguration validatorConfig : PluginListPropertyValueReader.effectivePluginListPropertyValue(pc, "validators", env, context)) {
         if (validatorConfig.isOfPluginClass(EmailAddressValidatorConfig.class)) {
            throw new PropertyConstraintViolationException(
               "validators",
               pc,
               env,
               context,
               "This item will be automatically validated by the \"Email Address Validator\". This validator must not be manually added to the list of validators."
            );
         }
      }
   }

   public Optional<StringValueProviderConfig> getInitialValueProvider() {
      return Optional.ofNullable(this.initialValueProvider);
   }

   public boolean isStealthUniquenessValidationNecessary() {
      return this.unique && this.enableStealthMode;
   }

   @AfterConfigActivation
   void initializeValidators() {
      List<StringValidatorConfig> emailValidators = new ArrayList<>(this.validators);
      emailValidators.add(
         ((EmailAddressValidatorConfig.EmailAddressValidatorConfigBuilder)PluginPropertyBuilderInitializer.pluginBuilder(
               EmailAddressValidatorConfig.EmailAddressValidatorConfigBuilder.class
            ))
            .maximumLength(this.maximumInputLength)
            .build()
      );
      this.initializedValidators = Collections.unmodifiableList(emailValidators);
   }

   public List<StringValidatorConfig> getValidators() {
      return this.initializedValidators;
   }

   public String getKey() {
      return this.key;
   }

   @Override
   public boolean isRequired() {
      return this.required;
   }

   public boolean isUnique() {
      return this.unique;
   }

   public boolean isEnableStealthMode() {
      return this.enableStealthMode;
   }

   @Override
   public Optional<String> getInputPurpose() {
      return Optional.ofNullable(this.inputPurpose);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class EmailItemDefinitionConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String key;
      @SuppressFBWarnings(justification = "generated code")
      private boolean required;
      @SuppressFBWarnings(justification = "generated code")
      private boolean unique;
      @SuppressFBWarnings(justification = "generated code")
      private boolean enableStealthMode;
      @SuppressFBWarnings(justification = "generated code")
      private int maximumInputLength;
      @SuppressFBWarnings(justification = "generated code")
      private StringValueProviderConfig initialValueProvider;
      @SuppressFBWarnings(justification = "generated code")
      private String inputPurpose;
      @SuppressFBWarnings(justification = "generated code")
      private List<StringValidatorConfig> validators;

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

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder key(String key) {
         this.key = key;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder required(boolean required) {
         this.required = required;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder unique(boolean unique) {
         this.unique = unique;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder enableStealthMode(boolean enableStealthMode) {
         this.enableStealthMode = enableStealthMode;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder maximumInputLength(int maximumInputLength) {
         this.maximumInputLength = maximumInputLength;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder initialValueProvider(StringValueProviderConfig initialValueProvider) {
         this.initialValueProvider = initialValueProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder inputPurpose(String inputPurpose) {
         this.inputPurpose = inputPurpose;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder validators(List<StringValidatorConfig> validators) {
         this.validators = validators;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public EmailItemDefinitionConfig build() {
         return new EmailItemDefinitionConfig(
            this.key,
            this.required,
            this.unique,
            this.enableStealthMode,
            this.maximumInputLength,
            this.initialValueProvider,
            this.inputPurpose,
            this.validators
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "EmailItemDefinitionConfig.EmailItemDefinitionConfigBuilder(key="
            + this.key
            + ", required="
            + this.required
            + ", unique="
            + this.unique
            + ", enableStealthMode="
            + this.enableStealthMode
            + ", maximumInputLength="
            + this.maximumInputLength
            + ", initialValueProvider="
            + this.initialValueProvider
            + ", inputPurpose="
            + this.inputPurpose
            + ", validators="
            + this.validators
            + ")";
      }
   }
}
