import { HttpStatus } from '@nestjs/common';
import { startOfDay } from 'date-fns';
import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
import { DEFAULT_ORG_TIMEZONE } from '../common/timezone/timezone.constants';
import { holidayResponseDto } from '../modules/holidays/holidays.dto';
import { createGeneralError } from './error-formatter';

export const normalizeHolidayDate = (
  value: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const year = Number(formatInTimeZone(value, timeZone, 'yyyy'));
  const month = Number(formatInTimeZone(value, timeZone, 'M')) - 1;
  const day = Number(formatInTimeZone(value, timeZone, 'd'));

  return new Date(Date.UTC(year, month, day));
};

export const getHolidayRangeLength = (
  startDate: Date,
  endDate: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  return Math.max(
    0,
    Math.round(
      (normalizeHolidayDate(endDate, timeZone).getTime() -
        normalizeHolidayDate(startDate, timeZone).getTime()) /
        (1000 * 60 * 60 * 24),
    ),
  );
};

export const isHolidayLeapYear = (year: number) => {
  return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
};

export const buildHolidayRecurringOccurrence = (
  baseDate: Date,
  year: number,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const month = Number(formatInTimeZone(baseDate, timeZone, 'M')) - 1;
  const day = Number(formatInTimeZone(baseDate, timeZone, 'd'));

  if (month === 1 && day === 29 && !isHolidayLeapYear(year)) {
    return null;
  }

  const occurrence = fromZonedTime(
    `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')} 00:00:00`,
    timeZone,
  );

  if (
    Number(formatInTimeZone(occurrence, timeZone, 'M')) - 1 !== month ||
    Number(formatInTimeZone(occurrence, timeZone, 'd')) !== day
  ) {
    return null;
  }

  return occurrence;
};

export const buildHolidayRecurringRange = (
  startDate: Date,
  endDate: Date,
  year: number,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const occurrenceStart = buildHolidayRecurringOccurrence(
    startDate,
    year,
    timeZone,
  );
  if (!occurrenceStart) {
    return null;
  }

  const occurrenceEnd = new Date(endDate);
  occurrenceEnd.setFullYear(year);

  return { occurrenceStart, occurrenceEnd };
};

export const includeHolidayDepartments = () => ({
  departments: true,
});

export const ensureHolidayDepartmentIdsExist = async (
  db: any,
  departmentIds: string[],
  owner_id: string,
) => {
  if (departmentIds.length === 0) {
    return;
  }

  const existingCount = await db.departments.count({
    where: {
      owner_id,
      id: {
        in: departmentIds,
      },
    },
  });

  if (existingCount !== departmentIds.length) {
    throw createGeneralError(
      'One or more departments are invalid',
      'department_ids',
      HttpStatus.BAD_REQUEST,
    );
  }
};

export const buildHolidayWhereFilter = (query: {
  holiday_type?: string;
  search?: string;
  is_recurring?: boolean;
}) => {
  const where: any = {};

  if (query.holiday_type) {
    where.holiday_type = query.holiday_type;
  }

  if (query.search) {
    where.name = {
      contains: query.search,
    };
  }

  if (typeof query.is_recurring === 'boolean') {
    where.is_recurring = query.is_recurring;
  }

  return where;
};

export const buildUpcomingHolidayRecurringDates = (
  baseDate: Date,
  count: number,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const dates: string[] = [];
  const now = startOfDay(new Date());
  const currentYear = Number(formatInTimeZone(now, timeZone, 'yyyy'));
  const baseYear = Number(formatInTimeZone(baseDate, timeZone, 'yyyy'));
  let year = Math.max(currentYear, baseYear);

  while (dates.length < count) {
    const occurrence = buildHolidayRecurringOccurrence(baseDate, year, timeZone);
    if (occurrence && occurrence >= now) {
      dates.push(occurrence.toISOString());
    }
    year += 1;
  }

  return dates;
};

export const buildHolidaySummary = (
  rows: Array<{
    start_date: Date;
    end_date: Date;
    is_recurring: boolean;
    holiday_type: string;
  }>,
) => {
  return rows.reduce(
    (acc, item) => {
      if (item.is_recurring) {
        acc.recurring_holidays += 1;
      }

      if (item.holiday_type === 'public') {
        acc.total_national_holidays += 1;
      }

      if (item.holiday_type === 'company') {
        acc.total_company_holidays += 1;
      }

      return acc;
    },
    {
      total_holidays: rows.length,
      recurring_holidays: 0,
      total_national_holidays: 0,
      total_company_holidays: 0,
    },
  );
};

export const getHolidayUtcMonthStartEnd = (
  year: number,
  month: number,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const localDate = `${year}-${String(month).padStart(2, '0')}-01 00:00:00`;
  const nextMonth = month === 12 ? 1 : month + 1;
  const nextYear = month === 12 ? year + 1 : year;

  const nextMonthStart = `${nextYear}-${String(nextMonth).padStart(
    2,
    '0',
  )}-01 00:00:00`;

  const monthStartUtc = fromZonedTime(localDate, timeZone);
  const nextMonthStartUtc = fromZonedTime(nextMonthStart, timeZone);
  return {
    startUtc: monthStartUtc,
    endUtc: new Date(nextMonthStartUtc.getTime() - 1),
  };
};

export const getHolidayUtcMonthStartEndFromDate = (
  date?: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const normalizedDate =
    date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date();
  const year = Number(formatInTimeZone(normalizedDate, timeZone, 'yyyy'));
  const month = Number(formatInTimeZone(normalizedDate, timeZone, 'M'));
  return getHolidayUtcMonthStartEnd(year, month, timeZone);
};

export const getCurrentMonthHolidays = (
  recurringHolidays: holidayResponseDto[],
  nonRecurringHolidays: holidayResponseDto[],
  startUtc: Date,
  endUtc: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const holidays: holidayResponseDto[] = [];

  for (const holiday of recurringHolidays) {
    const year = Number(formatInTimeZone(startUtc, timeZone, 'yyyy'));
    const occurrence = buildHolidayRecurringOccurrence(
      new Date(holiday.start_date),
      year,
      timeZone,
    );
    if (occurrence && occurrence >= startUtc && occurrence <= endUtc) {
      const occurrenceEnd = new Date(holiday?.end_date);
      occurrenceEnd.setFullYear(year);
      holidays.push({
        ...holiday,
        start_date: occurrence.toISOString(),
        end_date: occurrenceEnd.toISOString(),
      });
    }
  }
  return [...holidays, ...nonRecurringHolidays];
};
