import { addDays, eachDayOfInterval, format } from 'date-fns';
import { formatInTimeZone, toZonedTime } from 'date-fns-tz';
import { attendancesResponseDto } from '../modules/attendance/attendance.dto';
import { WorkScheduleResponseDto } from '../modules/configurations/configurations.dto';
import { holidaysResponseDto } from '../modules/holidays/holidays.dto';
import { leavesResponseDto } from '../modules/leaves/leaves.dto';
import { normalizeTimezone } from '../common/timezone/timezone.util';
import { DEFAULT_ORG_TIMEZONE } from '../common/timezone/timezone.constants';
import {
  classifyIndividualSummaryBucket,
  isGroupedPresent,
} from './attendance-summary.util';

const resolveScheduleTimezone = (workSchedule?: WorkScheduleResponseDto) =>
  normalizeTimezone(workSchedule?.timezone);

export const countHolidayFromStartAndEnd = (
  holidays: holidaysResponseDto,
  workSchedule: WorkScheduleResponseDto,
  start: Date,
  end: Date,
) => {
  const timeZone = resolveScheduleTimezone(workSchedule);
  const holidayDays = new Set<string>();

  const workingDays = workSchedule.working_days.map((day) => day.toLowerCase());

  for (const holiday of holidays) {
    const holidayStart = new Date(holiday.start_date);
    const holidayEnd = new Date(holiday.end_date);

    // Skip non-overlapping holidays
    if (holidayEnd < start || holidayStart > end) {
      continue;
    }

    // Clamp holiday range
    const rangeStart = holidayStart > start ? holidayStart : start;
    const rangeEnd = holidayEnd < end ? holidayEnd : end;

    const days = eachDayOfInterval({
      start: rangeStart,
      end: rangeEnd,
    });

    for (const day of days) {
      const bdDate = formatInTimeZone(day, timeZone, 'yyyy-MM-dd');
      const bdDayName = formatInTimeZone(day, timeZone, 'EEEE').toLowerCase();
      // Skip weekly off days
      if (!workingDays.includes(bdDayName)) {
        continue;
      }

      holidayDays.add(bdDate);
    }
  }

  return holidayDays.size;
};

export const getLeaveCountFromStartAndEnd = (
  leaves: any[],
  workSchedule: WorkScheduleResponseDto,
  start: Date,
  end: Date,
  checkWorkSchedule = true,
) => {
  const timeZone = resolveScheduleTimezone(workSchedule);
  const leaveDays = new Set<string>();
  const workingDays = workSchedule.working_days.map((day) => day.toLowerCase());

  for (const leave of leaves) {
    const leaveStart = new Date(leave.start_date);
    let leaveEnd = new Date(leave.end_date);

    // Midnight UTC = naive date with no TZ offset, subtract 1ms to exclude that day
    if (
      leaveEnd.getUTCHours() === 0 &&
      leaveEnd.getUTCMinutes() === 0 &&
      leaveEnd.getUTCSeconds() === 0 &&
      leaveEnd.getUTCMilliseconds() === 0
    ) {
      leaveEnd = new Date(leaveEnd.getTime() - 1);
    }

    if (leaveEnd < start || leaveStart > end) continue;

    const rangeStart = leaveStart > start ? leaveStart : start;
    const rangeEnd = leaveEnd < end ? leaveEnd : end;

    // Convert boundaries to BD date strings — avoids UTC day boundary issues
    const startBdStr = formatInTimeZone(rangeStart, timeZone, 'yyyy-MM-dd');
    const endBdStr = formatInTimeZone(rangeEnd, timeZone, 'yyyy-MM-dd');

    // Iterate purely in BD calendar days
    const current = new Date(startBdStr); // local date iteration
    const endDate = new Date(endBdStr);

    while (current <= endDate) {
      const bdDate = format(current, 'yyyy-MM-dd');
      const bdDayName = format(current, 'EEEE').toLowerCase();

      if (!checkWorkSchedule || workingDays.includes(bdDayName)) {
        leaveDays.add(bdDate);
      }

      current.setDate(current.getDate() + 1);
    }
  }

  return leaveDays.size;
};

export const getTotalWorkingDayFromStartAndEnd = (
  holidays: holidaysResponseDto,
  workSchedule: WorkScheduleResponseDto,
  start: Date,
  end: Date,
) => {
  const timeZone = resolveScheduleTimezone(workSchedule);
  console.log('Calculating total working days from', start, 'to', end);

  const holidayDays = new Set<string>();

  const workingDays = workSchedule.working_days.map((day) => day.toLowerCase());

  const zonedStart = toZonedTime(start, timeZone);
  const zonedEnd = toZonedTime(end, timeZone);

  // --------------------------------------------
  // Collect all holiday dates
  // --------------------------------------------
  for (const holiday of holidays) {
    const holidayStart = toZonedTime(new Date(holiday.start_date), timeZone);

    const holidayEnd = toZonedTime(new Date(holiday.end_date), timeZone);

    // Skip non-overlapping holidays
    if (holidayEnd < zonedStart || holidayStart > zonedEnd) {
      continue;
    }

    // Clamp holiday range
    const rangeStart = holidayStart > zonedStart ? holidayStart : zonedStart;

    const rangeEnd = holidayEnd < zonedEnd ? holidayEnd : zonedEnd;

    const holidayRangeDays = eachDayOfInterval({
      start: rangeStart,
      end: rangeEnd,
    });

    for (const day of holidayRangeDays) {
      const bdDate = formatInTimeZone(day, timeZone, 'yyyy-MM-dd');

      const bdDayName = formatInTimeZone(day, timeZone, 'EEEE').toLowerCase();

      // Ignore holidays on weekends
      if (!workingDays.includes(bdDayName)) {
        continue;
      }

      holidayDays.add(bdDate);
    }
  }

  // --------------------------------------------
  // Calculate working days
  // --------------------------------------------
  const totalDays = eachDayOfInterval({
    start: zonedStart,
    end: zonedEnd,
  });

  let workingDayCount = 0;

  for (const day of totalDays) {
    const bdDate = formatInTimeZone(day, timeZone, 'yyyy-MM-dd');

    const bdDayName = formatInTimeZone(day, timeZone, 'EEEE').toLowerCase();

    // Skip weekends
    if (!workingDays.includes(bdDayName)) {
      continue;
    }

    // Skip holidays
    if (holidayDays.has(bdDate)) {
      continue;
    }

    workingDayCount++;
  }

  return workingDayCount;
};

export const getAttendanceCountFromStartAndEnd = (
  attendances: attendancesResponseDto,
  start: Date,
  end: Date,
  currentDate: Date,
) => {
  let attendanceCount = 0;

  for (const attendance of attendances) {
    const attendanceDate = new Date(attendance.date);
    if (attendanceDate < start || attendanceDate > end) {
      continue;
    }

    const isToday =
      attendanceDate.toDateString() === currentDate.toDateString();

    if (isGroupedPresent(attendance.status, isToday)) {
      attendanceCount++;
    }
  }

  return attendanceCount;
};

export const getAttendanceDetailsCountFromStartAndEnd = (
  attendances: attendancesResponseDto,
  start: Date,
  end: Date,
  currentDate: Date,
) => {
  let attendanceCount = 0;
  let lateCount = 0;
  let halfDayCount = 0;

  for (const attendance of attendances) {
    const attendanceDate = new Date(attendance.date);
    if (attendanceDate < start || attendanceDate > end) {
      continue;
    }

    const isToday =
      attendanceDate.toDateString() === currentDate.toDateString();
    const bucket = classifyIndividualSummaryBucket(attendance.status, isToday);

    if (bucket === 'present') {
      attendanceCount++;
    } else if (bucket === 'late') {
      lateCount++;
    } else if (bucket === 'half_day') {
      halfDayCount++;
    }
  }

  return {
    attendance_count: attendanceCount,
    late_count: lateCount,
    half_day_count: halfDayCount,
  };
};

type DateType =
  | 'saturday'
  | 'sunday'
  | 'monday'
  | 'tuesday'
  | 'wednesday'
  | 'thursday'
  | 'friday';

export const getDaysBetweenInBd = (
  startUtc: Date,
  endUtc: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const startBd = formatInTimeZone(startUtc, timeZone, 'yyyy-MM-dd');

  const endBd = formatInTimeZone(endUtc, timeZone, 'yyyy-MM-dd');

  // Create LOCAL calendar dates
  let current = new Date(`${startBd}T00:00:00`);
  const end = new Date(`${endBd}T00:00:00`);

  const days: Date[] = [];

  while (current <= end) {
    days.push(new Date(current));
    current = addDays(current, 1);
  }

  return days;
};

export const getCurrentWeekDetails = (
  startUtc: Date,
  endUtc: Date,
  holidays: holidaysResponseDto,
  leaves: leavesResponseDto,
  workSchedule: WorkScheduleResponseDto,
) => {
  const timeZone = resolveScheduleTimezone(workSchedule);
  const weekDetails = getDaysBetweenInBd(startUtc, endUtc, timeZone).map((day) => {
    const bdDate = formatInTimeZone(day, timeZone, 'yyyy-MM-dd');
    const bdDayName = formatInTimeZone(day, timeZone, 'EEEE').toLowerCase();
    return {
      label: formatInTimeZone(day, timeZone, 'EEE'),
      date: bdDate,
      day: formatInTimeZone(day, timeZone, 'dd'),
      is_holiday: holidays.some((holiday) => {
        const holidayStart = formatInTimeZone(
          new Date(holiday.start_date),
          timeZone,
          'yyyy-MM-dd',
        );
        const holidayEnd = formatInTimeZone(
          new Date(holiday.end_date),
          timeZone,
          'yyyy-MM-dd',
        );
        return bdDate >= holidayStart && bdDate <= holidayEnd;
      }),
      is_leave: leaves.some((leave) => {
        const leaveStart = formatInTimeZone(
          new Date(leave.start_date),
          timeZone,
          'yyyy-MM-dd',
        );
        const leaveEnd = formatInTimeZone(
          new Date(leave.end_date),
          timeZone,
          'yyyy-MM-dd',
        );
        return bdDate >= leaveStart && bdDate <= leaveEnd;
      }),
      is_weekend: !workSchedule.working_days.includes(bdDayName as DateType),
      is_completed: new Date() > day,
      is_today:
        formatInTimeZone(new Date(), timeZone, 'yyyy-MM-dd') === bdDate,
    };
  });

  return weekDetails;
};

// export const getCurrentWeekDetails = (
//   startUtc: Date,
//   endUtc: Date,
//   attendances: attendancesResponseDto,
//   workSchedule?: WorkScheduleResponseDto,
// ) => {
//   const attendanceMap = new Map(
//     attendances.map((att) => [
//       formatInTimeZone(new Date(att.date!), TIME_ZONE, 'yyyy-MM-dd'),
//       att,
//     ]),
//   );

//   const weekDetails = getDaysBetweenInBd(startUtc, endUtc).map((day) => {
//     const bdDate = formatInTimeZone(day, TIME_ZONE, 'yyyy-MM-dd');

//     const bdDayName = formatInTimeZone(day, TIME_ZONE, 'EEEE').toLowerCase();

//     const isWeekend = workSchedule
//       ? !workSchedule.working_days.includes(bdDayName as DateType)
//       : false;

//     const attendance = attendanceMap.get(bdDate);

//     return {
//       label: formatInTimeZone(day, TIME_ZONE, 'EEE'),
//       date: bdDate,
//       day: formatInTimeZone(day, TIME_ZONE, 'dd'),
//       status: attendance ? attendance.status : 'absent',
//       is_completed: new Date() > day,
//       is_weekend: attendance?.status === 'weekend' || isWeekend || false,
//       is_holiday: attendance?.status === 'holiday' || false,
//       is_leave: attendance?.status === 'on_leave' || false,
//       is_today:
//         formatInTimeZone(new Date(), TIME_ZONE, 'yyyy-MM-dd') === bdDate,
//     };
//   });

//   return weekDetails;
// };

export const getIsTodayWeekend = (
  holidays: holidaysResponseDto,
  workSchedule: WorkScheduleResponseDto,
  startUtc: Date,
) => {
  const timeZone = resolveScheduleTimezone(workSchedule);
  const todayBdDayName = formatInTimeZone(
    startUtc,
    timeZone,
    'EEEE',
  ).toLowerCase();
  const isHoliday = holidays.some((holiday) => {
    const holidayStart = new Date(holiday.start_date);
    const holidayEnd = new Date(holiday.end_date);
    return startUtc >= holidayStart && startUtc <= holidayEnd;
  });
  const isWeekend = !workSchedule.working_days.includes(
    todayBdDayName as DateType,
  );
  return {
    isHoliday,
    isWeekend,
  };
};
