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

import com.airlock.iam.common.domain.model.user.profileitems.DateProfileItemLogic;
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.EnumProperty;
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.IntegerPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

@Plugin(
   description = "Plugin to hold a configurable user profile item of type date (potentially including time information). This will correspond to a text input field is added to the user's context data, provided that the property name matches the property name in the configured user data. The date format can be configured as well acceptable date ranges. These can be absolute dates or in number of days relative to the current day.",
   displayName = "Date User Profile Item"
)
public class DateUserProfileItemConfig extends UserProfileItemConfigBase {
   public static final String DATE_USER_PROFILE_ITEM_DISPLAY_NAME = "Date User Profile Item";
   @StringProperty(
      description = "Defines the date format of this item. The format is used for server-side validations and must correspond to other config options, such as \"Earliest Date\" and \"Latest Date\".",
      optional = true,
      displayName = "Date Format",
      allowedValues = {"dd.MM.yyyy", "dd-MM-yyyy", "MM-dd-yyyy"},
      defaultValue = "dd.MM.yyyy"
   )
   private String dateFormat;
   @SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR")
   private FastDateFormat dateFormatter;
   private static final String DATE_FORMAT_DISPLAY_NAME = "Date Format";
   @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 day 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_RELATIVE_DISPLAY_NAME = "Minimum Relative [days]";
   @StringProperty(
      description = "<p>The earliest date allowed to be filled in. This cannot be used together with \"Minimum Relative\" and must be in the same format as specified in \"Date Format\".</p>",
      optional = true,
      displayName = "Earliest Date",
      notCombinableWith = "minRelative"
   )
   private String minDate;
   public static final String MIN_DATE_DISPLAY_NAME = "Earliest Date";
   @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 day 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 the same format as specified in \"Date Format\".</p>",
      optional = true,
      displayName = "Latest Date",
      notCombinableWith = "maxRelative"
   )
   private String maxDate;
   private static final String MAX_DATE_DISPLAY_NAME = "Latest Date";
   @EnumProperty(
      description = "Defines how the date value should be transformed before it is persisted.<ul><li>DATE: the date value is transformed to a java.util.Date object (date with time, where all time values are set to 0).<br>Use 'Date And Time Context Data Item Config' for the corresponding context data item in the persister.</li><li>LOCAL_DATE: the date value is transformed to a java.time.LocalDate object (date only).<br>Use 'Local Date Context Data Item Config' for the corresponding context data item in the persister.</li><li>STRING: the date value is transformed to a String as defined by \"Date Format\".<br>Use 'String Context Data Item Config' for the corresponding context data item in the persister.</li></ul>",
      defaultValue = "LOCAL_DATE",
      optional = true
   )
   private DateProfileItemLogic.DateTransformation dateTransformation;

   public DateUserProfileItemConfig(
      String stringResourceKey,
      String propertyName,
      boolean optional,
      boolean modifiable,
      boolean validateOnlyChangedValues,
      boolean sortable,
      String dateFormat,
      DateProfileItemLogic.DateTransformation dateTransformation,
      Integer minRelative,
      String minDate,
      Integer maxRelative,
      String maxDate
   ) {
      super(stringResourceKey, propertyName, optional, modifiable, validateOnlyChangedValues, sortable);
      this.dateFormat = dateFormat;
      this.dateTransformation = dateTransformation;
      this.minRelative = minRelative;
      this.minDate = minDate;
      this.maxRelative = maxRelative;
      this.maxDate = maxDate;
   }

   @AfterConfigActivation
   void createDateFormatter() {
      this.dateFormatter = FastDateFormat.getInstance(this.dateFormat);
   }

   private static FastDateFormat getDateFormatValue(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      String dateFormat = StringPropertyValueReader.effectiveStringConfigValue(pc, "dateFormat", env, context);

      try {
         return FastDateFormat.getInstance(dateFormat);
      } catch (IllegalArgumentException e) {
         return null;
      }
   }

   @Nullable
   private static Date parseDate(FastDateFormat df, String date) {
      if (df != null && !StringUtils.isEmpty(date)) {
         try {
            return df.parse(date);
         } catch (ParseException e) {
            return null;
         }
      } else {
         return null;
      }
   }

   @ConfigurationValidator
   public static void checkDateFormats(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      FastDateFormat df = getDateFormatValue(pc, env, context);
      if (df != null) {
         String minDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "minDate", env, context);
         Date minDateObj = parseDate(df, minDate);
         if (StringUtils.isNotBlank(minDate) && minDateObj == null) {
            throw new PropertyConstraintViolationException("minDate", pc, env, context, "wrong date format.");
         }

         String maxDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "maxDate", env, context);
         Date maxDateObj = parseDate(df, maxDate);
         if (StringUtils.isNotBlank(maxDate) && maxDateObj == null) {
            throw new PropertyConstraintViolationException("maxDate", pc, env, context, "wrong date format.");
         }
      }
   }

   @ConfigurationValidator
   public static void checkNotEarlier(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valEnv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      Integer minRel = IntegerPropertyValueReader.effectiveIntegerConfigValue(pc, "minRelative", env, context);
      Integer maxRel = IntegerPropertyValueReader.effectiveIntegerConfigValue(pc, "maxRelative", env, context);
      if (minRel != null && maxRel != null && maxRel.compareTo(minRel) < 0) {
         throw new PropertyConstraintViolationException(
            "maxRelative", pc, env, context, "The maximal relative value cannot be smaller than the minimal relative value."
         );
      }

      String minDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "minDate", env, context);
      String maxDate = StringPropertyValueReader.effectiveStringConfigValue(pc, "maxDate", env, context);
      FastDateFormat df = getDateFormatValue(pc, env, context);
      Date maxDateObj = parseDate(df, maxDate);
      Date minDateObj = parseDate(df, minDate);
      if (minDateObj != null && maxDateObj != null && maxDateObj.before(minDateObj)) {
         throw new PropertyConstraintViolationException("maxDate", pc, env, context, "The maximal date cannot be before the minimal date.");
      }
   }

   @Override
   public UserProfileItemConfig.ProfileItemType getItemType() {
      return UserProfileItemConfig.ProfileItemType.DATE;
   }

   public DateProfileItemLogic getProfileItemLogic() {
      return DateProfileItemLogic.builder()
         .dateFormatter(this.dateFormatter)
         .dateTransformation(this.dateTransformation)
         .optional(this.isOptional())
         .modifiable(this.isModifiable())
         .maxDate(this.maxDate)
         .maxRelative(this.maxRelative)
         .minDate(this.minDate)
         .minRelative(this.minRelative)
         .validateOnlyChangedValues(this.isValidateOnlyChangedValues())
         .build();
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class DateUserProfileItemConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String stringResourceKey;
      @SuppressFBWarnings(justification = "generated code")
      private String propertyName;
      @SuppressFBWarnings(justification = "generated code")
      private boolean optional;
      @SuppressFBWarnings(justification = "generated code")
      private boolean modifiable;
      @SuppressFBWarnings(justification = "generated code")
      private boolean validateOnlyChangedValues;
      @SuppressFBWarnings(justification = "generated code")
      private boolean sortable;
      @SuppressFBWarnings(justification = "generated code")
      private String dateFormat;
      @SuppressFBWarnings(justification = "generated code")
      private DateProfileItemLogic.DateTransformation dateTransformation;
      @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")
      DateUserProfileItemConfigBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder stringResourceKey(String stringResourceKey) {
         this.stringResourceKey = stringResourceKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder propertyName(String propertyName) {
         this.propertyName = propertyName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder optional(boolean optional) {
         this.optional = optional;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder modifiable(boolean modifiable) {
         this.modifiable = modifiable;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder validateOnlyChangedValues(boolean validateOnlyChangedValues) {
         this.validateOnlyChangedValues = validateOnlyChangedValues;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder sortable(boolean sortable) {
         this.sortable = sortable;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder dateFormat(String dateFormat) {
         this.dateFormat = dateFormat;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig.DateUserProfileItemConfigBuilder dateTransformation(DateProfileItemLogic.DateTransformation dateTransformation) {
         this.dateTransformation = dateTransformation;
         return this;
      }

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public DateUserProfileItemConfig build() {
         return new DateUserProfileItemConfig(
            this.stringResourceKey,
            this.propertyName,
            this.optional,
            this.modifiable,
            this.validateOnlyChangedValues,
            this.sortable,
            this.dateFormat,
            this.dateTransformation,
            this.minRelative,
            this.minDate,
            this.maxRelative,
            this.maxDate
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "DateUserProfileItemConfig.DateUserProfileItemConfigBuilder(stringResourceKey="
            + this.stringResourceKey
            + ", propertyName="
            + this.propertyName
            + ", optional="
            + this.optional
            + ", modifiable="
            + this.modifiable
            + ", validateOnlyChangedValues="
            + this.validateOnlyChangedValues
            + ", sortable="
            + this.sortable
            + ", dateFormat="
            + this.dateFormat
            + ", dateTransformation="
            + this.dateTransformation
            + ", minRelative="
            + this.minRelative
            + ", minDate="
            + this.minDate
            + ", maxRelative="
            + this.maxRelative
            + ", maxDate="
            + this.maxDate
            + ")";
      }
   }
}
