package com.airlock.iam.base.misc.date;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import jakarta.annotation.Nullable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.time.FastDateFormat;

public final class Dates {
   private static final String DATE_TIME_FORMAT_PATTERN = "uuuu-MM-dd'T'HH:mm:ss.SSSXXX";
   private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("uuuu-MM-dd'T'HH:mm:ss.SSSXXX");
   private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX")
      .withResolverStyle(ResolverStyle.STRICT);
   private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT);
   private static final List<DateTimeFormatter> SUPPORTED_PARSERS = new ArrayList<>();

   private Dates() {
   }

   public static String toString(ZonedDateTime zonedDateTime) {
      Objects.requireNonNull(zonedDateTime);
      return zonedDateTime.format(DATE_TIME_FORMATTER);
   }

   @Nullable
   public static String toString(@Nullable Date date) {
      return date == null ? null : toString(date.toInstant());
   }

   public static String toString(Instant date) {
      return toString(ZonedDateTime.ofInstant(date, ZoneId.systemDefault()));
   }

   @Nullable
   public static Date toDate(@Nullable String dateString) {
      return dateString == null ? null : Date.from(parseInstant(dateString));
   }

   public static Instant parseInstant(String temporalString) {
      Objects.requireNonNull(temporalString);
      String normalizedTemporalString = temporalString.replace(' ', 'T');
      List<String> candidates = normalizedTemporalString.equals(temporalString) ? List.of(temporalString) : List.of(temporalString, normalizedTemporalString);
      DateTimeParseException lastException = null;

      for (String candidate : candidates) {
         for (DateTimeFormatter parser : SUPPORTED_PARSERS) {
            try {
               return Instant.from(parser.parse(candidate));
            } catch (DateTimeParseException e) {
               lastException = e;
            }
         }
      }

      throw new ViolationException(lastException);
   }

   public static LocalDate toLocalDate(String localDate) {
      return LocalDate.parse(localDate, LOCAL_DATE_FORMATTER);
   }

   public static String toString(LocalDate localDate) {
      return localDate.format(LOCAL_DATE_FORMATTER);
   }

   public static boolean isParsableTemporalString(String string) {
      if (string == null) {
         return false;
      }

      try {
         parseInstant(string);
         return true;
      } catch (ViolationException e) {
         return false;
      }
   }

   public static boolean isValidLocalDate(String localDateString) {
      try {
         toLocalDate(localDateString);
         return true;
      } catch (DateTimeParseException e) {
         return false;
      }
   }

   @Nullable
   public static Date toDate(@Nullable Object value) {
      if (value == null) {
         return null;
      } else if (value instanceof Date date) {
         return date;
      } else if (value instanceof Instant instant) {
         return Date.from(instant);
      } else if (value instanceof Long longValue) {
         return new Date(longValue);
      } else if (value instanceof String str) {
         return toDate(str);
      } else {
         throw new ViolationException(String.format("Cannot cast/convert %s to date.", value));
      }
   }

   @Nullable
   public static Instant toInstant(@Nullable Date date) {
      return date == null ? null : date.toInstant();
   }

   public static String getDatePattern() {
      return DATE_FORMAT.getPattern().replace("uuuu", "yyyy");
   }

   private static DateTimeFormatter formatterWithTimeZoneOffset(String timeZonePattern) {
      return new DateTimeFormatterBuilder()
         .parseCaseInsensitive()
         .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
         .appendOffset(timeZonePattern, "Z")
         .toFormatter();
   }

   static {
      SUPPORTED_PARSERS.add(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
      SUPPORTED_PARSERS.add(formatterWithTimeZoneOffset("+HHMM"));
      SUPPORTED_PARSERS.add(formatterWithTimeZoneOffset("+HH"));
   }
}
