package com.airlock.iam.common.domain.model.user.profileitems;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.date.LocalDateHelper;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nullable;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

public class DateProfileItemLogic implements UserProfileItemLogic {
   private static final ELogger LOG = ELoggerFactory.getLogger(DateProfileItemLogic.class);
   private final boolean validateOnlyChangedValues;
   private final DateProfileItemLogic.DateTransformation dateTransformation;
   private final boolean optional;
   private final boolean modifiable;
   private final Integer minRelative;
   private final String minDate;
   private final Integer maxRelative;
   private final String maxDate;
   private final FastDateFormat dateFormatter;

   @Override
   public UserProfileItemLogic.CheckResult validate(Object newValue, Object oldValue, String uid) {
      if (!UserProfileItemLogic.validationRequired(newValue, oldValue, this.validateOnlyChangedValues)) {
         return UserProfileItemLogic.CheckResult.IS_OK;
      }

      LocalDate oldDate;
      try {
         oldDate = this.convertToLocalDate(oldValue);
      } catch (ParseException e) {
         oldDate = null;
      }

      LocalDate newDate;
      try {
         newDate = this.convertToLocalDate(newValue);
      } catch (ParseException e) {
         LOG.debug("validate: Parse exception while converting date: " + e.getMessage());
         return UserProfileItemLogic.CheckResult.WRONG_FORMAT;
      }

      if (!UserProfileItemLogic.validationRequired(newDate, oldDate, this.validateOnlyChangedValues)) {
         return UserProfileItemLogic.CheckResult.IS_OK;
      } else if (newDate == null) {
         return !this.optional ? UserProfileItemLogic.CheckResult.REQUIRED : UserProfileItemLogic.CheckResult.IS_OK;
      } else {
         return !this.valueInsideRange(newDate) ? UserProfileItemLogic.CheckResult.OUTSIDE_RANGE : UserProfileItemLogic.CheckResult.IS_OK;
      }
   }

   private boolean valueInsideRange(LocalDate value) {
      LocalDate minRel = this.getRelativeDate(this.minRelative);
      if (minRel != null && value.isBefore(minRel)) {
         LOG.debug("Date " + value + " is before min. relative of " + minRel + " (" + this.minRelative + " days from now)");
         return false;
      } else {
         LocalDate maxRel = this.getRelativeDate(this.maxRelative);
         if (maxRel != null && value.isAfter(maxRel)) {
            LOG.debug("Date " + value + " is after max. relative of " + maxRel + " (" + this.maxRelative + " days from now)");
            return false;
         } else {
            LocalDate minAbs = this.parseDate(this.minDate);
            if (minAbs != null && value.isBefore(minAbs)) {
               LOG.debug("Date " + value + " is before min. date of " + this.minDate);
               return false;
            } else {
               LocalDate maxAbs = this.parseDate(this.maxDate);
               if (maxAbs != null && value.isAfter(maxAbs)) {
                  LOG.debug("Date " + value + " is after max. date of " + this.maxDate);
                  return false;
               } else {
                  return true;
               }
            }
         }
      }
   }

   @Nullable
   private LocalDate parseDate(String date) {
      if (this.dateFormatter != null && date != null) {
         try {
            return LocalDateHelper.toLocalDate(this.dateFormatter.parse(date));
         } catch (ParseException e) {
            return null;
         }
      } else {
         return null;
      }
   }

   private LocalDate getRelativeDate(Integer days) {
      return days == null ? null : LocalDate.now().plus(days.intValue(), ChronoUnit.DAYS);
   }

   private LocalDate convertToLocalDate(Object obj) throws ParseException {
      if (obj == null) {
         return null;
      } else if (obj instanceof LocalDate) {
         return (LocalDate)obj;
      } else if (obj instanceof Date) {
         return LocalDateHelper.toLocalDate((Date)obj);
      } else if (!(obj instanceof String str)) {
         throw new ParseException("not a String or Date", -1);
      } else {
         return StringUtils.isBlank(str) ? null : LocalDateHelper.toLocalDate(this.dateFormatter.parse(str));
      }
   }

   @Override
   public Object formatForPersistence(Object value) {
      switch (this.dateTransformation) {
         case STRING:
            return this.formatAsString(value);
         case DATE:
            return this.formatAsDate(value);
         case LOCAL_DATE:
            return this.formatAsLocalDate(value);
         default:
            throw new ViolationException("Unknown date transformation: " + this.dateTransformation);
      }
   }

   private Date formatAsDate(Object value) {
      return LocalDateHelper.toDate(this.formatAsLocalDate(value));
   }

   private LocalDate formatAsLocalDate(Object value) {
      try {
         return this.convertToLocalDate(value);
      } catch (ParseException e) {
         LOG.debug("formatAsDate: Parse exception while converting date: " + e.getMessage());
         return null;
      }
   }

   private String formatAsString(Object value) {
      try {
         Date date = LocalDateHelper.toDate(this.convertToLocalDate(value));
         return date == null ? "" : this.dateFormatter.format(date);
      } catch (ParseException e) {
         LOG.debug("formatAsString: Parse exception while converting date: " + e.getMessage());
         return Objects.toString(value, "");
      }
   }

   public LocalDate transformPersistentValue(Object value) {
      return this.formatAsLocalDate(value);
   }

   @Override
   public boolean isModifiable(Object value) {
      return this.modifiable;
   }

   @SuppressFBWarnings(justification = "generated code")
   DateProfileItemLogic(
      boolean validateOnlyChangedValues,
      DateProfileItemLogic.DateTransformation dateTransformation,
      boolean optional,
      boolean modifiable,
      Integer minRelative,
      String minDate,
      Integer maxRelative,
      String maxDate,
      FastDateFormat dateFormatter
   ) {
      this.validateOnlyChangedValues = validateOnlyChangedValues;
      this.dateTransformation = dateTransformation;
      this.optional = optional;
      this.modifiable = modifiable;
      this.minRelative = minRelative;
      this.minDate = minDate;
      this.maxRelative = maxRelative;
      this.maxDate = maxDate;
      this.dateFormatter = dateFormatter;
   }

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

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

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

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

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

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

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

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

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

   @SuppressFBWarnings(justification = "generated code")
   public FastDateFormat getDateFormatter() {
      return this.dateFormatter;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class DateProfileItemLogicBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private boolean validateOnlyChangedValues;
      @SuppressFBWarnings(justification = "generated code")
      private DateProfileItemLogic.DateTransformation dateTransformation;
      @SuppressFBWarnings(justification = "generated code")
      private boolean optional;
      @SuppressFBWarnings(justification = "generated code")
      private boolean modifiable;
      @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")
      private FastDateFormat dateFormatter;

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

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

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

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

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

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

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public DateProfileItemLogic.DateProfileItemLogicBuilder dateFormatter(FastDateFormat dateFormatter) {
         this.dateFormatter = dateFormatter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public DateProfileItemLogic build() {
         return new DateProfileItemLogic(
            this.validateOnlyChangedValues,
            this.dateTransformation,
            this.optional,
            this.modifiable,
            this.minRelative,
            this.minDate,
            this.maxRelative,
            this.maxDate,
            this.dateFormatter
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "DateProfileItemLogic.DateProfileItemLogicBuilder(validateOnlyChangedValues="
            + this.validateOnlyChangedValues
            + ", dateTransformation="
            + this.dateTransformation
            + ", optional="
            + this.optional
            + ", modifiable="
            + this.modifiable
            + ", minRelative="
            + this.minRelative
            + ", minDate="
            + this.minDate
            + ", maxRelative="
            + this.maxRelative
            + ", maxDate="
            + this.maxDate
            + ", dateFormatter="
            + this.dateFormatter
            + ")";
      }
   }

   public enum DateTransformation {
      STRING,
      DATE,
      LOCAL_DATE;
   }
}
