import {
  addSeconds,
  endOfDay,
  isAfter,
  isBefore,
  isDate,
  isEqual,
  isValid as isValidDate,
  isWithinInterval,
  startOfDay,
  subSeconds,
} from 'date-fns';

import { EntryType, TimePairs } from 'src/types/timeTracking';
import { parseLocalDateWithTimezone } from 'src/utils/date';
import {
  addDayToIsoString,
  isCurrentDay,
  timestampInReferenceDate,
} from 'src/utils/timeTracking';

type EnclosedValidationParams = {
  pairDates: TimePairs;
  referenceDate: string;
  reservedCycles: TimePairs[];
  applyNightShift: boolean;
};

export const validateNoEnclosedCycles = ({
  pairDates,
  referenceDate,
  reservedCycles,
  applyNightShift,
}: EnclosedValidationParams) => {
  // If the entry is a night shift, we need to add a day to the date to compare
  const endDateReference = applyNightShift
    ? addDayToIsoString(referenceDate)
    : referenceDate;
  const referencedStartDate = new Date(
    timestampInReferenceDate(pairDates.start, referenceDate),
  );
  const valuesInReference = {
    start: referencedStartDate,
    end: isValidDate(pairDates.end)
      ? new Date(timestampInReferenceDate(pairDates.end, endDateReference))
      : endOfDay(new Date(addDayToIsoString(referenceDate))),
  };
  return !reservedCycles.some(c =>
    isWithinInterval(new Date(c?.end || c.start), {
      start: addSeconds(valuesInReference.start, 1),
      end: subSeconds(valuesInReference.end, 1),
    }),
  );
};

export const validateNoFutureEntries = (
  value: Date | null,
  summaryDay: string,
  timezone: string,
  entryWithNightShift: boolean,
) => {
  // If the entry is a night shift, we need to add a day to the date to compare
  const inputDate = entryWithNightShift
    ? new Date(value).setDate(value.getDate() + 1)
    : value;
  const timezonedDate = parseLocalDateWithTimezone(new Date(), timezone);
  return isCurrentDay(summaryDay) && isValidDate(inputDate)
    ? isBefore(inputDate, timezonedDate)
    : true;
};

export const validateNoRepeatedSameTypeEntry = (
  value: Date | null,
  type: EntryType,
  reservedCycles: TimePairs[],
  applyingNightShift: boolean,
) => {
  if (isValidDate(value) && reservedCycles.length > 0) {
    return (
      applyingNightShift ||
      !reservedCycles.some(c => {
        const reservedTypeDate = new Date(c[type.toLowerCase()]);
        return (
          reservedTypeDate.getHours() === value.getHours() &&
          reservedTypeDate.getMinutes() === value.getMinutes()
        );
      })
    );
  }
  return true;
};

type OrderProps = {
  date: Date | null;
  dateToCompare: Date | null;
  referenceDate: string;
  order: 'before' | 'after';
  enableNightShift: boolean;
};

// This is an alternate 'validateIsAfterDate' from validations in which the date from timePicker is adjusted to a reference
export const validateDateOrder = ({
  date,
  dateToCompare,
  referenceDate,
  order,
  enableNightShift,
}: OrderProps) => {
  const dateLimit = order === 'after' ? startOfDay : endOfDay;
  const referencedDate = new Date(
    timestampInReferenceDate(date, referenceDate),
  );
  const referencedCompareDate = new Date(
    timestampInReferenceDate(dateToCompare || dateLimit(date), referenceDate),
  );
  if (!isDate(date) || !isDate(dateToCompare) || enableNightShift) {
    return true;
  }
  return (order === 'after' ? isAfter : isBefore)(
    referencedDate,
    referencedCompareDate,
  );
};

export const validateNoEqualDates = (
  value: Date | null,
  dateToCompare: Date | null,
) => !isEqual(value, dateToCompare);

// "comparingTimestamps" should be used when "date" is managed via a TimePicker which only uses today
// and "datesRange" is composed by  other dates/timestamps which we want to check against
export const validateDatesCollision = (
  date: Date | string,
  datesRange: { start: string; end: string }[],
  comparingTimestamps: boolean,
  nightShift: boolean,
) => {
  if (!isDate(date) || datesRange.length === 0) {
    return true;
  }
  const dateToCheck = new Date(date);
  if (comparingTimestamps) {
    // We want to find a range that has a defined date (entry)
    const definedCycle = datesRange.find(d => d.start || d.end);
    const referenceDate = definedCycle.start
      ? new Date(definedCycle.start)
      : new Date(definedCycle.end);
    dateToCheck.setFullYear(referenceDate.getFullYear());
    dateToCheck.setMonth(referenceDate.getMonth());
    // If the entry is a night shift, we need to add a day to the date to compare
    const referenceDay = nightShift
      ? referenceDate.getDate() + 1
      : referenceDate.getDate();
    dateToCheck.setDate(referenceDay);
  }
  // It could happen that one of the range's dates are undefined so in that case we compare against start/end of date
  return !datesRange.some(range =>
    isWithinInterval(dateToCheck, {
      start: addSeconds(
        range.start ? new Date(range.start) : startOfDay(new Date(range.end)),
        1,
      ),
      end: subSeconds(
        range.end ? new Date(range.end) : endOfDay(new Date(range.start)),
        1,
      ),
    }),
  );
};
