package com.airlock.iam.common.application.configuration.termsofservice;

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.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
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.api.valuereader.StringPropertyValueReader;
import com.airlock.iam.plugin.framework.application.configuration.validator.FastDateFormatPatternValidator;
import com.google.common.collect.Sets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Set;

@Plugin(
   description = "<p>Configures the terms of services (legal disclaimer) that has to be accepted by the user during self-registration or when accessing an application.</p><p>When accessing a target application, the Loginapp checks whether the currently valid terms of services have been accepted by the user. The currently valid terms of services are referenced by the \"TOS-tag\" defined in this configuration. The accepted terms of services is stored in a user property (within context data container).</p><p>In order to perform the check, the login application must be configured such that it is involved every time an application is accessed for the first time within a session. Usually, this means that only the credentials/roles needed for the currently accessed application are granted to the Airlock Gateway (WAF) session.</p>"
)
@PluginLicenseRestriction(moduleAffiliation = "TermsOfServices")
public class TermsOfServiceConfig {
   public static final String TOS_STORAGE_PRIVATE = "Private property";
   public static final String TOS_STORAGE_SHARED_COMMA_SEPARATED = "Shared property (comma-separated)";
   public static final String TOS_STORAGE_SHARED_MULTIVALUE = "Shared property (multi-value)";
   @StringProperty(
      description = "<p>An arbitrary string uniquely defining the currently valid legal disclaimer used for this application. The tag may be shared among several applications or with the self-registration process. Once the user accepts the disclaimer, this tag is saved with the user data (see next properties). If the user data has no or a different tag saved, the user is prompted to accept the current disclaimer.</p><p>The tag may not contain commas (\",\") or the timestamp separator (in case the timestamp pattern is specified).</p>",
      optional = false,
      displayName = "Terms Of Service Tag",
      exampleValues = {"AGB_2009", "TOS-1.2.1"}
   )
   private String tag;
   @StringProperty(
      description = "Defines how the terms of services tag is stored in the user's context data container:<ul> <li>Shared property (comma-separated) : The context data property (see below) is shared among several applications. The accepted tags are stored in a comma-separated value list.</li> <li>Private property : The context data property (see below) is used only by this application.</li> <li>Shared property (multi-value) : The context data property (see below) is shared among several applications. The accepted tags are stored in a multi-value attribute.</li></ul>",
      optional = true,
      defaultValue = "Shared property (comma-separated)",
      allowedValues = {"Shared property (comma-separated)", "Private property", "Shared property (multi-value)"}
   )
   private String storageStrategy;
   @StringProperty(
      description = "If set, a timestamp is stored with the tag in the user's context data container using the specified date format.<br/>For example resulting in: <code>TOS-2013:20130828134559+0100</code>",
      optional = true,
      suggestedValues = {"yyyyMMddHHmmssZ", "yyyyMMdd"}
   )
   private String timestampFormat;
   private static final String TIMESTAMP_FORMAT_PROPERTY = "timestampFormat";
   @StringProperty(
      description = "If a timestamp is saved, this property specifies the separator used between the tag and the timestamp.<br/>Notice that if storing them using Shared property (comma-separated), the comma cannot be used.",
      optional = true,
      defaultValue = ":",
      maxLength = 1
   )
   private String timestampSeparator;
   @StringProperty(
      description = "The name of the property in the user's context data container holding the accepted tag(s).<br/>Make sure that the used persister is configured to read/write the property!",
      optional = false,
      suggestedValues = "disclaimer_tag"
   )
   private String userPropertyName;
   @PluginListProperty(
      description = "<p>Defines a language-dependent, formatted disclaimer text displayed to the user.</p><p>If the user accessing an application and the terms of services have to be accepted, the whole terms / legal disclaimer can be displayed on the page.</p><p>If the user has to accept the terms of services during the self-registration, it makes sense to show only a link to the terms / legal disclaimer (it's only a checkbox label).</p>",
      optional = false,
      minElements = 1
   )
   private List<DisclaimerTextConfig> disclaimerTexts;
   public static final String DISCLAIMER_TEXTS_FIELD = "disclaimerTexts";

   @ConfigurationValidator
   public static void checkTagContainsInvalidChars(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      String tag = StringPropertyValueReader.effectiveStringConfigValue(config, "tag", env, context);
      String storageStrategy = StringPropertyValueReader.effectiveStringConfigValue(config, "storageStrategy", env, context);
      String timestampFormat = StringPropertyValueReader.effectiveStringConfigValue(config, "timestampFormat", env, context);
      String timestampSeparator = StringPropertyValueReader.effectiveStringConfigValue(config, "timestampSeparator", env, context);
      if (timestampFormat != null) {
         FastDateFormatPatternValidator.validateFastDateFormatPattern(config, "timestampFormat", env, context);
         if (tag.contains(timestampSeparator)) {
            throw new PropertyConstraintViolationException("tag", config, env, context, "The tag may not contain the timestamp separator");
         }

         if ("Shared property (comma-separated)".equals(storageStrategy) && ",".equals(timestampSeparator)) {
            throw new PropertyConstraintViolationException(
               "timestampSeparator", config, env, context, "The timestamp separator may not be the comma when using the comma-separated storage strategy."
            );
         }
      }

      if (tag.indexOf(44) > -1) {
         throw new PropertyConstraintViolationException("tag", config, env, context, "The tag may not contain commas (\",\")");
      }
   }

   @ConfigurationValidator
   public static void checkLanguages(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      List<PluginConfiguration> textConfigs = PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "disclaimerTexts", env, context);
      Set<String> usedLanguages = Sets.newHashSet();

      for (PluginConfiguration textConfig : textConfigs) {
         if (textConfig != null) {
            String language = textConfig.getSimplePropertyConfigurationValue("language", env, context);
            if (language != null && !usedLanguages.add(language)) {
               throw new PropertyConstraintViolationException(
                  "disclaimerTexts", config, env, context, "The disclaimer texts define multiple texts for the same language. This is not allowed."
               );
            }
         }
      }
   }

   public TermsOfService getLogic() {
      return new TermsOfService(this);
   }

   public String getTag() {
      return this.tag;
   }

   public String getStorageStrategy() {
      return this.storageStrategy;
   }

   @Nullable
   public String getTimestampFormat() {
      return this.timestampFormat;
   }

   public String getTimestampSeparator() {
      return this.timestampSeparator;
   }

   public String getUserPropertyName() {
      return this.userPropertyName;
   }

   public List<DisclaimerTextConfig> getDisclaimerTexts() {
      return this.disclaimerTexts;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public TermsOfServiceConfig(
      String tag,
      String storageStrategy,
      String timestampFormat,
      String timestampSeparator,
      String userPropertyName,
      List<DisclaimerTextConfig> disclaimerTexts
   ) {
      this.tag = tag;
      this.storageStrategy = storageStrategy;
      this.timestampFormat = timestampFormat;
      this.timestampSeparator = timestampSeparator;
      this.userPropertyName = userPropertyName;
      this.disclaimerTexts = disclaimerTexts;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class TermsOfServiceConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String tag;
      @SuppressFBWarnings(justification = "generated code")
      private String storageStrategy;
      @SuppressFBWarnings(justification = "generated code")
      private String timestampFormat;
      @SuppressFBWarnings(justification = "generated code")
      private String timestampSeparator;
      @SuppressFBWarnings(justification = "generated code")
      private String userPropertyName;
      @SuppressFBWarnings(justification = "generated code")
      private List<DisclaimerTextConfig> disclaimerTexts;

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

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder tag(String tag) {
         this.tag = tag;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder storageStrategy(String storageStrategy) {
         this.storageStrategy = storageStrategy;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder timestampFormat(String timestampFormat) {
         this.timestampFormat = timestampFormat;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder timestampSeparator(String timestampSeparator) {
         this.timestampSeparator = timestampSeparator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder userPropertyName(String userPropertyName) {
         this.userPropertyName = userPropertyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig.TermsOfServiceConfigBuilder disclaimerTexts(List<DisclaimerTextConfig> disclaimerTexts) {
         this.disclaimerTexts = disclaimerTexts;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TermsOfServiceConfig build() {
         return new TermsOfServiceConfig(
            this.tag, this.storageStrategy, this.timestampFormat, this.timestampSeparator, this.userPropertyName, this.disclaimerTexts
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TermsOfServiceConfig.TermsOfServiceConfigBuilder(tag="
            + this.tag
            + ", storageStrategy="
            + this.storageStrategy
            + ", timestampFormat="
            + this.timestampFormat
            + ", timestampSeparator="
            + this.timestampSeparator
            + ", userPropertyName="
            + this.userPropertyName
            + ", disclaimerTexts="
            + this.disclaimerTexts
            + ")";
      }
   }
}
