import NumberUtil from "./NumberUtil";

const DAYS_IN_A_MONTH = 28;

const DateUtil = {
  addDaysToDate(date: Date, days: number): Date {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
  },
  addHoursToDate(date: Date, hours: number): Date {
    const result = new Date(date);
    result.setHours(result.getHours() + hours);
    return result;
  },
  addMinutesToDate(date: Date, minutes: number): Date {
    const result = new Date(date);
    result.setMinutes(result.getMinutes() + minutes);
    return result;
  },
  addMonthsToDate(date: Date | string, months: number, isUTCDate?: boolean) {
    let currentDate = new Date(date);
    const currentDay = DateUtil.getUTCDate(currentDate, isUTCDate);

    currentDate.setMonth(currentDate.getMonth() + months, currentDay);
    if (
      DateUtil.isMonthChangeUTCTimeDifference(
        currentDate,
        currentDay,
        isUTCDate,
      ) ||
      DateUtil.hasInvalidNoOfDaysInMonth(currentDate, currentDay)
    ) {
      currentDate.setDate(0);
    }

    const decimal = NumberUtil.decimalPart(months);
    const daysToAdd = Math.floor(decimal * DAYS_IN_A_MONTH);
    if (daysToAdd > 0) {
      currentDate = this.addDaysToDate(currentDate, daysToAdd);
    }

    return currentDate;
  },
  areDatesEqual(date1: Date, date2: Date): boolean {
    return (
      date1.getFullYear() === date2.getFullYear() &&
      date1.getMonth() === date2.getMonth() &&
      date1.getDate() === date2.getDate()
    );
  },
  areDatesSameHour(date1: Date, date2: Date): boolean {
    return (
      this.areDatesEqual(date1, date2) && date1.getHours() === date2.getHours()
    );
  },
  getDate(value?: Date | string | null): Date | null {
    if (value instanceof Date) {
      return value;
    }
    if (typeof value !== "string") {
      return null;
    }
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) {
      return null;
    }
    return date;
  },
  getDateWithSubtractedDays(date: Date, daysToSubtract: number): Date {
    const newDate = new Date(date);
    newDate.setDate(newDate.getDate() - daysToSubtract);
    return newDate;
  },
  getDaysBetweenDates(initialDate_: Date, futureDate_: Date): number {
    const initialDate = this.zerodate(initialDate_);
    const futureDate = this.zerodate(futureDate_);

    const timeDiff = futureDate.getTime() - initialDate.getTime();
    const daysDiff = Math.floor(timeDiff / (1_000 * 3_600 * 24));

    return daysDiff;
  },
  getFirstDayOfYear: (year: number) => new Date(year, 0, 1),
  getLastDayOfYear: (year: number) => new Date(year, 11, 31),
  getMonthsBetweenDates(initialDate: Date, futureDate: Date): number {
    let months = (futureDate.getFullYear() - initialDate.getFullYear()) * 12;
    months -= initialDate.getMonth();
    months += futureDate.getMonth();

    return months <= 0 ? -1 : months;
  },
  getMonthsUntilDate(futureDate: Date): number {
    const today = new Date();
    return this.getMonthsBetweenDates(today, futureDate);
  },
  getUTCDate: (date: Date, isUTCDate = false) => {
    return isUTCDate ? date.getUTCDate() : date.getDate();
  },
  hasInvalidNoOfDaysInMonth: (calculatedDate: Date, currentDay: number) => {
    return calculatedDate.getDate() < currentDay;
  },
  isDateInRangeIgnoringYear(
    date: Date,
    startDate: Date,
    endDate: Date,
  ): boolean {
    const currentDate = this.zerodate(date);
    const startDateIgnoringYear = this.zerodate(startDate);
    startDateIgnoringYear.setFullYear(currentDate.getFullYear());
    const endDateIgnoringYear = this.zerodate(endDate);
    endDateIgnoringYear.setFullYear(currentDate.getFullYear());

    if (endDateIgnoringYear < startDateIgnoringYear) {
      endDateIgnoringYear.setFullYear(endDateIgnoringYear.getFullYear() + 1);
      if (currentDate < startDateIgnoringYear) {
        currentDate.setFullYear(currentDate.getFullYear() + 1);
      }
    }

    const isUsageFromInPast = startDateIgnoringYear <= currentDate;
    const isUsageToInFuture = endDateIgnoringYear >= currentDate;
    const isInRange = isUsageFromInPast && isUsageToInFuture;
    return isInRange;
  },
  isInFuture(date?: Date | null): boolean {
    const parsedDate = DateUtil.getDate(date);
    return parsedDate !== null && parsedDate > new Date();
  },
  isInPast(date?: Date | null): boolean {
    const parsedDate = DateUtil.getDate(date);
    return parsedDate !== null && parsedDate < new Date();
  },
  isMonthChangeUTCTimeDifference: (
    currentDate: Date,
    currentDay: number,
    isUTCDate?: boolean,
  ) => {
    return (
      DateUtil.getUTCDate(currentDate, isUTCDate) < currentDay &&
      currentDate.getUTCMonth() < currentDate.getMonth()
    );
  },
  zerodate<T extends Date | null>(date: T): T {
    if (date === null) {
      return null as T;
    }
    const value = new Date(date);
    value.setHours(0, 0, 0, 0);
    return value as T;
  },
  zerominutes<T extends Date | null>(date: T): T {
    if (date === null) {
      return null as T;
    }
    const value = new Date(date);
    value.setMinutes(0, 0, 0);
    return value as T;
  },
};

export default DateUtil;
