import { HttpStatus, Injectable } from '@nestjs/common';
import { createGeneralError } from '../../utils';

@Injectable()
export class MembersHelper {
  normalizeDate(value: Date) {
    return new Date(
      Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()),
    );
  }

  startOfYear(value: Date) {
    return new Date(Date.UTC(value.getUTCFullYear(), 0, 1));
  }

  parseWorkingDays(workingDays: unknown) {
    if (!Array.isArray(workingDays)) {
      throw createGeneralError(
        'Working schedule is invalid for this owner',
        'working_days',
        HttpStatus.BAD_REQUEST,
      );
    }

    const dayNameToIndex: Record<string, number> = {
      sunday: 0,
      monday: 1,
      tuesday: 2,
      wednesday: 3,
      thursday: 4,
      friday: 5,
      saturday: 6,
    };

    const parsed = new Set<number>();

    for (const day of workingDays) {
      if (typeof day !== 'string') {
        continue;
      }

      const normalized = day.trim().toLowerCase();
      const index = dayNameToIndex[normalized];
      if (typeof index === 'number') {
        parsed.add(index);
      }
    }

    if (parsed.size === 0) {
      throw createGeneralError(
        'Working days must include at least one valid day',
        'working_days',
        HttpStatus.BAD_REQUEST,
      );
    }

    return parsed;
  }

  calculateAssignedLeaveDays(
    policy: {
      annual_leave_days: number;
      sick_leave_days: number;
      casual_leave_days: number;
      maternity_leave_days: number;
      paternity_leave_days: number;
      personal_leave_days: number;
      compensatory_leave_days: number;
    } | null,
  ) {
    if (!policy) {
      return 0;
    }

    return (
      policy.annual_leave_days +
      policy.sick_leave_days +
      policy.casual_leave_days +
      policy.maternity_leave_days +
      policy.paternity_leave_days +
      policy.personal_leave_days +
      policy.compensatory_leave_days
    );
  }

  private isLeapYear(year: number) {
    return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
  }

  private buildRecurringOccurrence(baseDate: Date, year: number) {
    const month = baseDate.getUTCMonth();
    const day = baseDate.getUTCDate();

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

    const occurrence = new Date(Date.UTC(year, month, day));

    if (occurrence.getUTCMonth() !== month || occurrence.getUTCDate() !== day) {
      return null;
    }

    return occurrence;
  }

  private addDays(value: Date, days: number) {
    const next = new Date(value);
    next.setUTCDate(next.getUTCDate() + days);
    return next;
  }

  private getRangeLength(startDate: Date, endDate: Date) {
    return Math.max(
      0,
      Math.round(
        (this.normalizeDate(endDate).getTime() -
          this.normalizeDate(startDate).getTime()) /
          (1000 * 60 * 60 * 24),
      ),
    );
  }

  private buildRecurringRange(startDate: Date, endDate: Date, year: number) {
    const occurrenceStart = this.buildRecurringOccurrence(startDate, year);
    if (!occurrenceStart) {
      return null;
    }

    const occurrenceEnd = this.addDays(
      occurrenceStart,
      this.getRangeLength(startDate, endDate),
    );

    return { occurrenceStart, occurrenceEnd };
  }

  buildHolidayDateSet(
    from: Date,
    to: Date,
    holidays: Array<{
      start_date: Date;
      end_date: Date;
      is_recurring: boolean;
    }>,
  ) {
    const dates = new Set<string>();

    for (const holiday of holidays) {
      const holidayStart = this.normalizeDate(holiday.start_date);
      const holidayEnd = this.normalizeDate(holiday.end_date);

      if (holiday.is_recurring) {
        const startYear = Math.max(
          from.getUTCFullYear(),
          holidayStart.getUTCFullYear(),
        );

        for (let year = startYear; year <= to.getUTCFullYear(); year += 1) {
          const occurrence = this.buildRecurringRange(
            holidayStart,
            holidayEnd,
            year,
          );
          if (!occurrence) {
            continue;
          }

          if (
            occurrence.occurrenceEnd < from ||
            occurrence.occurrenceStart > to
          ) {
            continue;
          }

          const current = new Date(occurrence.occurrenceStart);
          while (current <= occurrence.occurrenceEnd) {
            if (current >= from && current <= to) {
              dates.add(this.normalizeDate(current).toISOString());
            }
            current.setUTCDate(current.getUTCDate() + 1);
          }
        }
        continue;
      }

      const current = new Date(holidayStart);
      while (current <= holidayEnd) {
        if (current >= from && current <= to) {
          dates.add(this.normalizeDate(current).toISOString());
        }
        current.setUTCDate(current.getUTCDate() + 1);
      }
    }

    return dates;
  }

  countWorkingDays(
    from: Date,
    to: Date,
    holidayDates: Set<string>,
    workingDayIndexes: Set<number>,
  ) {
    const current = new Date(from);
    let count = 0;

    while (current <= to) {
      const normalized = this.normalizeDate(current).toISOString();
      const dayIndex = current.getUTCDay();

      if (workingDayIndexes.has(dayIndex) && !holidayDates.has(normalized)) {
        count += 1;
      }

      current.setUTCDate(current.getUTCDate() + 1);
    }

    return count;
  }

  countLeaveDays(
    from: Date,
    to: Date,
    leaves: Array<{ start_date: Date; end_date: Date | null }>,
  ) {
    return leaves.reduce((total, leave) => {
      const leaveStart = this.normalizeDate(leave.start_date);
      const leaveEnd = this.normalizeDate(leave.end_date ?? leave.start_date);
      const overlapStart = leaveStart > from ? leaveStart : from;
      const overlapEnd = leaveEnd < to ? leaveEnd : to;

      if (overlapStart > overlapEnd) {
        return total;
      }

      const dayCount =
        Math.floor(
          (overlapEnd.getTime() - overlapStart.getTime()) /
            (1000 * 60 * 60 * 24),
        ) + 1;

      return total + dayCount;
    }, 0);
  }
  buildResponse(data: unknown, total: number, page: number, limit: number) {
    const totalPages = Math.ceil(total / limit);
    return {
      data,
      message: 'Members filtered successfully',
      status: HttpStatus.OK,
      details: {
        pagination: {
          total_items: total,
          total_pages: totalPages,
          current_page: page,
          items_per_page: limit,
          has_next_page: page < totalPages,
          has_previous_page: page > 1,
          next_page: page < totalPages ? page + 1 : (null as null),
          previous_page: page > 1 ? page - 1 : (null as null),
        },
      },
    };
  }
}
