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

import com.airlock.iam.base.misc.date.Dates;
import com.airlock.iam.common.api.application.configuration.validation.DateAndTimeValidatorConfig;
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.property.IntegerProperty;
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.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

@Plugin(
   description = "<p>Validate a date and time.</p>\n<ul>\n<li>If the date and time value was not specified, the corresponding error type is <code>REQUIRED</code>.</li>\n<li>If the specified date and time is before the minimum, the corresponding error type is <code>BEFORE_MIN_DATE</code>.</li>\n<li>If the specified date and time is after the maximum, the corresponding error type is <code>AFTER_MAX_DATE</code>.</li>\n</ul>",
   displayName = "Date And Time Validator",
   typeShortName = "DateAndTimeValidator"
)
public final class DefaultDateAndTimeValidatorConfig implements DateAndTimeValidatorConfig {
   @IntegerProperty(
      description = "<p>The \"minimum relative value\" is the lower limit (earliest possible) for allowed difference in days to the current date.</p><p>Examples: A value of 1 means that tomorrow is the earliest possible date to enter, a value of -365 means that the entered date can be at most one year in the past.</p>",
      exampleValues = {-365, -1, 7, 730},
      optional = true,
      notCombinableWith = "minDate",
      displayName = "Minimum Relative [days]"
   )
   private Integer minRelative;
   public static final String MIN_DATE = "minDate";
   @StringProperty(
      description = "<p>The earliest date allowed to be filled in. This cannot be used together with \"Minimum Relative\" and must be in ISO 8601 format.</p>",
      optional = true,
      exampleValues = {"2011-12-03T10:15:30.000+01:00", "2018-02-06T15:58:53.661Z"},
      notCombinableWith = "minRelative"
   )
   private String minDate;
   @IntegerProperty(
      description = "<p>The \"maximum relative value\" is the upper limit (last possible) for allowed difference in days to the current date. This cannot be used together with \"Max Date\".</p><p>Examples: A value of 1 means that tomorrow is the latest possible date to enter, a value of -365 means that the entered date has to be at least one year in the past. Use this property to configure a minimal required age.</p>",
      exampleValues = {-365, -1, 7, 730},
      optional = true,
      notCombinableWith = "maxDate",
      displayName = "Maximum Relative [days]"
   )
   private Integer maxRelative;
   @StringProperty(
      description = "<p>The latest date allowed to be filled in. This cannot be used together with \"Maximum Relative\" and it must be in ISO 8601 format.</p>",
      optional = true,
      exampleValues = {"2011-12-03T10:15:30.000+01:00", "2018-02-06T15:58:53.661Z"},
      notCombinableWith = "maxRelative"
   )
   private String maxDate;
   public static final String MAX_DATE = "maxDate";

   public Optional<Duration> getMinRelative() {
      return Optional.ofNullable(this.minRelative).map(Duration::ofDays);
   }

   public Optional<Instant> getMinDate() {
      return Optional.ofNullable(this.minDate).map(Dates::parseInstant);
   }

   public Optional<Duration> getMaxRelative() {
      return Optional.ofNullable(this.maxRelative).map(Duration::ofDays);
   }

   public Optional<Instant> getMaxDate() {
      return Optional.ofNullable(this.maxDate).map(Dates::parseInstant);
   }

   @ConfigurationValidator
   public static void checkDateFormats(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      String minDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "minDate", env, context);
      if (!Strings.isNullOrEmpty(minDate) && !Dates.isParsableTemporalString(minDate)) {
         throw new PropertyConstraintViolationException("minDate", pc, env, context, "Value must be in ISO 8601 format.");
      }

      String maxDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "maxDate", env, context);
      if (!Strings.isNullOrEmpty(maxDate) && !Dates.isParsableTemporalString(maxDate)) {
         throw new PropertyConstraintViolationException("maxDate", pc, env, context, "Value must be in ISO 8601 format.");
      }
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public DefaultDateAndTimeValidatorConfig(Integer minRelative, String minDate, Integer maxRelative, String maxDate) {
      this.minRelative = minRelative;
      this.minDate = minDate;
      this.maxRelative = maxRelative;
      this.maxDate = maxDate;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class DefaultDateAndTimeValidatorConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Integer minRelative;
      @SuppressFBWarnings(justification = "generated code")
      private String minDate;
      @SuppressFBWarnings(justification = "generated code")
      private Integer maxRelative;
      @SuppressFBWarnings(justification = "generated code")
      private String maxDate;

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

      @SuppressFBWarnings(justification = "generated code")
      public DefaultDateAndTimeValidatorConfig.DefaultDateAndTimeValidatorConfigBuilder minRelative(Integer minRelative) {
         this.minRelative = minRelative;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DefaultDateAndTimeValidatorConfig.DefaultDateAndTimeValidatorConfigBuilder minDate(String minDate) {
         this.minDate = minDate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DefaultDateAndTimeValidatorConfig.DefaultDateAndTimeValidatorConfigBuilder maxRelative(Integer maxRelative) {
         this.maxRelative = maxRelative;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DefaultDateAndTimeValidatorConfig.DefaultDateAndTimeValidatorConfigBuilder maxDate(String maxDate) {
         this.maxDate = maxDate;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DefaultDateAndTimeValidatorConfig build() {
         return new DefaultDateAndTimeValidatorConfig(this.minRelative, this.minDate, this.maxRelative, this.maxDate);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "DefaultDateAndTimeValidatorConfig.DefaultDateAndTimeValidatorConfigBuilder(minRelative="
            + this.minRelative
            + ", minDate="
            + this.minDate
            + ", maxRelative="
            + this.maxRelative
            + ", maxDate="
            + this.maxDate
            + ")";
      }
   }
}
