import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ForbiddenException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import { CreateAbsenceDto } from './dto/create-absence.dto';
import { UpdateAbsenceDto } from './dto/update-absence.dto';
import {
  QueryAbsencesDto,
  QueryCalendarDto,
  QueryConflictsDto,
} from './dto/query-absences.dto';
import { ApproveAbsenceDto, RejectAbsenceDto } from './dto/approve-absence.dto';
import { Prisma, AbsenceType, ApprovalStatus, ContractType } from '@prisma/client';
import { calculateWorkingDays, DEFAULT_VACATION_DAYS } from '@tos/shared';
import { JwtPayload } from '../../../auth/interfaces/jwt-payload.interface';

// Constants for vacation calculations
const PROBATION_MONTHS = 6;
const DAYS_PER_MONTH_ENTITLEMENT = DEFAULT_VACATION_DAYS / 12;

// Absence types that count against vacation balance
const VACATION_TYPES: AbsenceType[] = [AbsenceType.VACATION];

// Absence types that don't require approval
const AUTO_APPROVED_TYPES: AbsenceType[] = [AbsenceType.SICK, AbsenceType.SICK_CHILD];

interface AbsenceEvent {
  type:
    | 'absence.created'
    | 'absence.approved'
    | 'absence.rejected'
    | 'absence.cancelled'
    | 'absence.updated';
  payload: {
    absenceId: string;
    employeeId: string;
    absenceType: AbsenceType;
    startDate: Date;
    endDate: Date;
    status: ApprovalStatus;
    approvedById?: string;
    comment?: string;
    reason?: string;
  };
}

export interface VacationBalance {
  employeeId: string;
  employeeName: string;
  year: number;
  entitlement: number;
  taken: number;
  planned: number;
  remaining: number;
  carryOver: number;
  details: {
    type: string;
    startDate: Date;
    endDate: Date;
    days: number;
    status: ApprovalStatus;
  }[];
}

export interface CalendarEntry {
  id: string;
  employeeId: string;
  employeeName: string;
  type: AbsenceType;
  startDate: Date;
  endDate: Date;
  days: number;
  status: ApprovalStatus;
  departmentId?: string;
  departmentName?: string;
}

export interface ConflictResult {
  hasConflicts: boolean;
  conflicts: {
    employeeId: string;
    employeeName: string;
    absenceId: string;
    type: AbsenceType;
    startDate: Date;
    endDate: Date;
    overlapDays: number;
  }[];
  teamCoverage?: {
    totalEmployees: number;
    absentCount: number;
    coveragePercentage: number;
  };
}

@Injectable()
export class AbsencesService {
  private readonly logger = new Logger(AbsencesService.name);

  // Event queue for notifications (to be connected to event emitter later)
  private eventQueue: AbsenceEvent[] = [];

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Create a new absence request
   */
  async create(createDto: CreateAbsenceDto, currentUser: JwtPayload) {
    // Determine the employee
    let employeeId = createDto.employeeId;

    if (!employeeId) {
      // Get employee from current user
      const employee = await this.prisma.employee.findUnique({
        where: { userId: currentUser.sub },
      });

      if (!employee) {
        throw new BadRequestException(
          'No employee record found for current user',
        );
      }
      employeeId = employee.id;
    }

    // Validate employee exists
    const employee = await this.prisma.employee.findUnique({
      where: { id: employeeId },
      include: {
        user: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            departmentId: true,
          },
        },
      },
    });

    if (!employee) {
      throw new NotFoundException(`Employee with ID ${employeeId} not found`);
    }

    // Check if creating for self or someone else
    if (createDto.employeeId && createDto.employeeId !== employeeId) {
      // Creating for another employee requires approval permission
      const isManager = await this.isManagerOfEmployee(
        currentUser.sub,
        employeeId,
      );
      if (!isManager && !currentUser.roles.includes('admin') && !currentUser.roles.includes('hr-manager')) {
        throw new ForbiddenException(
          'You can only create absences for yourself or employees you manage',
        );
      }
    }

    // Parse dates
    const startDate = new Date(createDto.startDate);
    const endDate = new Date(createDto.endDate);

    // Validate dates
    if (endDate < startDate) {
      throw new BadRequestException('End date must be after or equal to start date');
    }

    // Calculate days if not provided
    const days = createDto.days ?? calculateWorkingDays(startDate, endDate);

    if (days <= 0) {
      throw new BadRequestException('Absence must be at least 0.5 days');
    }

    // Check for overlapping absences
    const conflicts = await this.checkEmployeeConflicts(
      employeeId,
      startDate,
      endDate,
    );

    if (conflicts.hasConflicts) {
      throw new BadRequestException(
        `Absence overlaps with existing absence(s): ${conflicts.conflicts.map((c) => c.absenceId).join(', ')}`,
      );
    }

    // Check vacation balance for vacation type
    if (VACATION_TYPES.includes(createDto.type)) {
      const balance = await this.getBalance(employeeId, startDate.getFullYear());
      if (balance.remaining < days) {
        throw new BadRequestException(
          `Insufficient vacation balance. Remaining: ${balance.remaining} days, Requested: ${days} days`,
        );
      }
    }

    // Determine initial status
    let status: ApprovalStatus = ApprovalStatus.PENDING;
    let approvedById: string | undefined;

    // Auto-approve sick days
    if (AUTO_APPROVED_TYPES.includes(createDto.type)) {
      status = ApprovalStatus.APPROVED;
      // No approver for auto-approved absences
    }

    // Create absence
    const absence = await this.prisma.absence.create({
      data: {
        type: createDto.type,
        startDate,
        endDate,
        days,
        status,
        note: createDto.note,
        employeeId,
        approvedById,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
                departmentId: true,
              },
            },
          },
        },
        approvedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    // Queue event for notification
    this.queueEvent({
      type: 'absence.created',
      payload: {
        absenceId: absence.id,
        employeeId: absence.employeeId,
        absenceType: absence.type,
        startDate: absence.startDate,
        endDate: absence.endDate,
        status: absence.status,
      },
    });

    return absence;
  }

  /**
   * Find all absences with filtering and pagination
   */
  async findAll(query: QueryAbsencesDto, currentUser: JwtPayload) {
    const {
      page = 1,
      limit = 20,
      employeeId,
      type,
      status,
      startDateFrom,
      startDateTo,
      endDateFrom,
      endDateTo,
      departmentId,
      ownOnly,
      sortBy,
      sortOrder,
    } = query;
    const skip = (page - 1) * limit;

    // Build where clause
    const where: Prisma.AbsenceWhereInput = {};

    // Apply filters
    if (employeeId) {
      where.employeeId = employeeId;
    }

    if (type) {
      where.type = type;
    }

    if (status) {
      where.status = status;
    }

    // Date range filters
    if (startDateFrom || startDateTo) {
      where.startDate = {
        ...(startDateFrom && { gte: new Date(startDateFrom) }),
        ...(startDateTo && { lte: new Date(startDateTo) }),
      };
    }

    if (endDateFrom || endDateTo) {
      where.endDate = {
        ...(endDateFrom && { gte: new Date(endDateFrom) }),
        ...(endDateTo && { lte: new Date(endDateTo) }),
      };
    }

    // Department filter
    if (departmentId) {
      where.employee = {
        user: {
          departmentId,
        },
      };
    }

    // Permission-based filtering
    if (ownOnly || this.shouldFilterOwnOnly(currentUser)) {
      const employee = await this.prisma.employee.findUnique({
        where: { userId: currentUser.sub },
      });
      if (employee) {
        where.employeeId = employee.id;
      }
    } else if (this.canViewDepartmentOnly(currentUser)) {
      // Team leads can only view their department
      const user = await this.prisma.user.findUnique({
        where: { id: currentUser.sub },
        select: { departmentId: true },
      });
      if (user?.departmentId) {
        where.employee = {
          user: {
            departmentId: user.departmentId,
          },
        };
      }
    }

    // Build order by
    const orderBy: Prisma.AbsenceOrderByWithRelationInput = sortBy
      ? { [sortBy]: sortOrder || 'desc' }
      : { startDate: 'desc' };

    // Execute query
    const [absences, total] = await Promise.all([
      this.prisma.absence.findMany({
        where,
        skip,
        take: limit,
        orderBy,
        include: {
          employee: {
            include: {
              user: {
                select: {
                  id: true,
                  firstName: true,
                  lastName: true,
                  email: true,
                  departmentId: true,
                  department: {
                    select: {
                      id: true,
                      name: true,
                    },
                  },
                },
              },
            },
          },
          approvedBy: {
            select: {
              id: true,
              firstName: true,
              lastName: true,
              email: true,
            },
          },
        },
      }),
      this.prisma.absence.count({ where }),
    ]);

    return {
      data: absences,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Find a single absence by ID
   */
  async findOne(id: string, currentUser: JwtPayload) {
    const absence = await this.prisma.absence.findUnique({
      where: { id },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
                departmentId: true,
                department: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
          },
        },
        approvedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    if (!absence) {
      throw new NotFoundException(`Absence with ID ${id} not found`);
    }

    // Check view permission
    await this.checkViewPermission(absence, currentUser);

    return absence;
  }

  /**
   * Update an absence (only PENDING status)
   */
  async update(id: string, updateDto: UpdateAbsenceDto, currentUser: JwtPayload) {
    const absence = await this.findOne(id, currentUser);

    // Only PENDING absences can be updated
    if (absence.status !== ApprovalStatus.PENDING) {
      throw new BadRequestException(
        `Cannot update absence with status ${absence.status}. Only PENDING absences can be updated.`,
      );
    }

    // Check if user can update
    await this.checkUpdatePermission(absence, currentUser);

    // Parse dates if provided
    const startDate = updateDto.startDate
      ? new Date(updateDto.startDate)
      : absence.startDate;
    const endDate = updateDto.endDate
      ? new Date(updateDto.endDate)
      : absence.endDate;

    // Validate dates
    if (endDate < startDate) {
      throw new BadRequestException('End date must be after or equal to start date');
    }

    // Calculate days if dates changed
    const days =
      updateDto.days ??
      (updateDto.startDate || updateDto.endDate
        ? calculateWorkingDays(startDate, endDate)
        : absence.days);

    // Check for conflicts with other absences
    const conflicts = await this.checkEmployeeConflicts(
      absence.employeeId,
      startDate,
      endDate,
      id,
    );

    if (conflicts.hasConflicts) {
      throw new BadRequestException(
        `Updated dates overlap with existing absence(s): ${conflicts.conflicts.map((c) => c.absenceId).join(', ')}`,
      );
    }

    // Check vacation balance if type changes or days increase
    const newType = updateDto.type ?? absence.type;
    if (VACATION_TYPES.includes(newType)) {
      const balance = await this.getBalance(
        absence.employeeId,
        startDate.getFullYear(),
      );
      const currentDays = VACATION_TYPES.includes(absence.type)
        ? absence.days
        : 0;
      const additionalDays = Number(days) - Number(currentDays);

      if (additionalDays > 0 && balance.remaining < additionalDays) {
        throw new BadRequestException(
          `Insufficient vacation balance. Remaining: ${balance.remaining} days, Additional requested: ${additionalDays} days`,
        );
      }
    }

    // Update absence
    const updatedAbsence = await this.prisma.absence.update({
      where: { id },
      data: {
        type: updateDto.type,
        startDate,
        endDate,
        days,
        note: updateDto.note,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
                departmentId: true,
              },
            },
          },
        },
        approvedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    // Queue event
    this.queueEvent({
      type: 'absence.updated',
      payload: {
        absenceId: updatedAbsence.id,
        employeeId: updatedAbsence.employeeId,
        absenceType: updatedAbsence.type,
        startDate: updatedAbsence.startDate,
        endDate: updatedAbsence.endDate,
        status: updatedAbsence.status,
      },
    });

    return updatedAbsence;
  }

  /**
   * Cancel an absence (soft delete)
   */
  async cancel(id: string, currentUser: JwtPayload) {
    const absence = await this.findOne(id, currentUser);

    // Only PENDING or APPROVED absences can be cancelled
    const cancellableStatuses: ApprovalStatus[] = [ApprovalStatus.PENDING, ApprovalStatus.APPROVED];
    if (!cancellableStatuses.includes(absence.status)) {
      throw new BadRequestException(
        `Cannot cancel absence with status ${absence.status}`,
      );
    }

    // Check if user can cancel
    await this.checkCancelPermission(absence, currentUser);

    // Update status to CANCELLED
    const cancelledAbsence = await this.prisma.absence.update({
      where: { id },
      data: {
        status: ApprovalStatus.CANCELLED,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
              },
            },
          },
        },
      },
    });

    // Queue event
    this.queueEvent({
      type: 'absence.cancelled',
      payload: {
        absenceId: cancelledAbsence.id,
        employeeId: cancelledAbsence.employeeId,
        absenceType: cancelledAbsence.type,
        startDate: cancelledAbsence.startDate,
        endDate: cancelledAbsence.endDate,
        status: cancelledAbsence.status,
      },
    });

    return cancelledAbsence;
  }

  /**
   * Approve an absence
   */
  async approve(
    id: string,
    approveDto: ApproveAbsenceDto,
    currentUser: JwtPayload,
  ) {
    const absence = await this.findOne(id, currentUser);

    // Only PENDING absences can be approved
    if (absence.status !== ApprovalStatus.PENDING) {
      throw new BadRequestException(
        `Cannot approve absence with status ${absence.status}. Only PENDING absences can be approved.`,
      );
    }

    // Check approval permission
    await this.checkApprovalPermission(absence, currentUser);

    // Update status to APPROVED
    const approvedAbsence = await this.prisma.absence.update({
      where: { id },
      data: {
        status: ApprovalStatus.APPROVED,
        approvedById: currentUser.sub,
        note: approveDto.comment
          ? `${absence.note ? absence.note + '\n\n' : ''}[Approver: ${approveDto.comment}]`
          : absence.note,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
              },
            },
          },
        },
        approvedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    // Queue event
    this.queueEvent({
      type: 'absence.approved',
      payload: {
        absenceId: approvedAbsence.id,
        employeeId: approvedAbsence.employeeId,
        absenceType: approvedAbsence.type,
        startDate: approvedAbsence.startDate,
        endDate: approvedAbsence.endDate,
        status: approvedAbsence.status,
        approvedById: currentUser.sub,
        comment: approveDto.comment,
      },
    });

    return approvedAbsence;
  }

  /**
   * Reject an absence
   */
  async reject(id: string, rejectDto: RejectAbsenceDto, currentUser: JwtPayload) {
    const absence = await this.findOne(id, currentUser);

    // Only PENDING absences can be rejected
    if (absence.status !== ApprovalStatus.PENDING) {
      throw new BadRequestException(
        `Cannot reject absence with status ${absence.status}. Only PENDING absences can be rejected.`,
      );
    }

    // Check approval permission
    await this.checkApprovalPermission(absence, currentUser);

    // Update status to REJECTED
    const rejectedAbsence = await this.prisma.absence.update({
      where: { id },
      data: {
        status: ApprovalStatus.REJECTED,
        approvedById: currentUser.sub,
        note: rejectDto.reason
          ? `${absence.note ? absence.note + '\n\n' : ''}[Rejected: ${rejectDto.reason}]`
          : absence.note,
      },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
              },
            },
          },
        },
        approvedBy: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            email: true,
          },
        },
      },
    });

    // Queue event
    this.queueEvent({
      type: 'absence.rejected',
      payload: {
        absenceId: rejectedAbsence.id,
        employeeId: rejectedAbsence.employeeId,
        absenceType: rejectedAbsence.type,
        startDate: rejectedAbsence.startDate,
        endDate: rejectedAbsence.endDate,
        status: rejectedAbsence.status,
        approvedById: currentUser.sub,
        reason: rejectDto.reason,
      },
    });

    return rejectedAbsence;
  }

  /**
   * Get pending absences for approval
   */
  async getPending(currentUser: JwtPayload) {
    const where: Prisma.AbsenceWhereInput = {
      status: ApprovalStatus.PENDING,
    };

    // Filter by managed employees for team leads
    if (!currentUser.roles.includes('admin') && !currentUser.roles.includes('hr-manager')) {
      const managedDepartments = await this.prisma.department.findMany({
        where: { managerId: currentUser.sub },
        select: { id: true },
      });

      if (managedDepartments.length > 0) {
        where.employee = {
          user: {
            departmentId: {
              in: managedDepartments.map((d) => d.id),
            },
          },
        };
      } else {
        // No managed departments, return empty
        return { data: [], meta: { total: 0 } };
      }
    }

    const absences = await this.prisma.absence.findMany({
      where,
      orderBy: [{ startDate: 'asc' }, { createdAt: 'asc' }],
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                email: true,
                departmentId: true,
                department: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    return {
      data: absences,
      meta: {
        total: absences.length,
      },
    };
  }

  /**
   * Get calendar data (approved absences)
   */
  async getCalendar(query: QueryCalendarDto, currentUser: JwtPayload): Promise<CalendarEntry[]> {
    const { startDate, endDate, departmentId, types } = query;

    // Default to current month if no dates provided
    const now = new Date();
    const start = startDate
      ? new Date(startDate)
      : new Date(now.getFullYear(), now.getMonth(), 1);
    const end = endDate
      ? new Date(endDate)
      : new Date(now.getFullYear(), now.getMonth() + 1, 0);

    // Parse types filter
    const typeFilter = types
      ? (types.split(',').filter((t) => Object.values(AbsenceType).includes(t as AbsenceType)) as AbsenceType[])
      : undefined;

    const where: Prisma.AbsenceWhereInput = {
      status: ApprovalStatus.APPROVED,
      OR: [
        {
          startDate: { lte: end },
          endDate: { gte: start },
        },
      ],
    };

    if (typeFilter && typeFilter.length > 0) {
      where.type = { in: typeFilter };
    }

    if (departmentId) {
      where.employee = {
        user: {
          departmentId,
        },
      };
    }

    // Permission-based filtering
    if (this.canViewDepartmentOnly(currentUser)) {
      const user = await this.prisma.user.findUnique({
        where: { id: currentUser.sub },
        select: { departmentId: true },
      });
      if (user?.departmentId) {
        where.employee = {
          user: {
            departmentId: user.departmentId,
          },
        };
      }
    }

    const absences = await this.prisma.absence.findMany({
      where,
      orderBy: { startDate: 'asc' },
      include: {
        employee: {
          include: {
            user: {
              select: {
                id: true,
                firstName: true,
                lastName: true,
                departmentId: true,
                department: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    return absences.map((absence) => ({
      id: absence.id,
      employeeId: absence.employeeId,
      employeeName: `${absence.employee.user.firstName} ${absence.employee.user.lastName}`,
      type: absence.type,
      startDate: absence.startDate,
      endDate: absence.endDate,
      days: Number(absence.days),
      status: absence.status,
      departmentId: absence.employee.user.departmentId ?? undefined,
      departmentName: absence.employee.user.department?.name,
    }));
  }

  /**
   * Get vacation balance for an employee
   */
  async getBalance(employeeId: string, year?: number): Promise<VacationBalance> {
    const currentYear = year ?? new Date().getFullYear();

    const employee = await this.prisma.employee.findUnique({
      where: { id: employeeId },
      include: {
        user: {
          select: {
            firstName: true,
            lastName: true,
          },
        },
      },
    });

    if (!employee) {
      throw new NotFoundException(`Employee with ID ${employeeId} not found`);
    }

    // Calculate entitlement based on entry date
    const entitlement = this.calculateEntitlement(
      employee.entryDate,
      employee.contractType,
      currentYear,
    );

    // Get all vacation absences for the year
    const yearStart = new Date(currentYear, 0, 1);
    const yearEnd = new Date(currentYear, 11, 31);

    const absences = await this.prisma.absence.findMany({
      where: {
        employeeId,
        type: { in: VACATION_TYPES },
        startDate: { gte: yearStart },
        endDate: { lte: yearEnd },
        status: { not: ApprovalStatus.CANCELLED },
      },
      orderBy: { startDate: 'asc' },
    });

    // Calculate taken (approved) and planned (pending)
    const taken = absences
      .filter((a) => a.status === ApprovalStatus.APPROVED)
      .reduce((sum, a) => sum + Number(a.days), 0);

    const planned = absences
      .filter((a) => a.status === ApprovalStatus.PENDING)
      .reduce((sum, a) => sum + Number(a.days), 0);

    // TODO: Implement carry-over from previous year
    const carryOver = 0;

    const remaining = entitlement + carryOver - taken - planned;

    return {
      employeeId,
      employeeName: `${employee.user.firstName} ${employee.user.lastName}`,
      year: currentYear,
      entitlement,
      taken,
      planned,
      remaining,
      carryOver,
      details: absences.map((a) => ({
        type: a.type,
        startDate: a.startDate,
        endDate: a.endDate,
        days: Number(a.days),
        status: a.status,
      })),
    };
  }

  /**
   * Check for conflicts
   */
  async checkConflicts(
    query: QueryConflictsDto,
    currentUser: JwtPayload,
  ): Promise<ConflictResult> {
    const { employeeId, departmentId, startDate, endDate, excludeId } = query;

    if (!startDate || !endDate) {
      throw new BadRequestException('Start date and end date are required');
    }

    const start = new Date(startDate);
    const end = new Date(endDate);

    // Employee-level conflicts
    if (employeeId) {
      return this.checkEmployeeConflicts(employeeId, start, end, excludeId);
    }

    // Department-level conflicts (team coverage)
    if (departmentId) {
      return this.checkDepartmentConflicts(departmentId, start, end, excludeId);
    }

    // Default: check for current user's employee
    const employee = await this.prisma.employee.findUnique({
      where: { userId: currentUser.sub },
    });

    if (!employee) {
      throw new BadRequestException('No employee record found for current user');
    }

    return this.checkEmployeeConflicts(employee.id, start, end, excludeId);
  }

  // ============================================================================
  // Private Helper Methods
  // ============================================================================

  /**
   * Check if user is manager of the given employee
   */
  private async isManagerOfEmployee(
    userId: string,
    employeeId: string,
  ): Promise<boolean> {
    const employee = await this.prisma.employee.findUnique({
      where: { id: employeeId },
      include: {
        user: {
          select: { departmentId: true },
        },
      },
    });

    if (!employee?.user.departmentId) {
      return false;
    }

    const department = await this.prisma.department.findUnique({
      where: { id: employee.user.departmentId },
    });

    return department?.managerId === userId;
  }

  /**
   * Calculate vacation entitlement based on entry date and contract type
   */
  private calculateEntitlement(
    entryDate: Date,
    contractType: ContractType,
    year: number,
  ): number {
    const yearStart = new Date(year, 0, 1);
    const yearEnd = new Date(year, 11, 31);

    // If entry date is in the future year, no entitlement
    if (entryDate > yearEnd) {
      return 0;
    }

    // Calculate months worked in the year
    let monthsWorked = 12;

    if (entryDate.getFullYear() === year) {
      // Entered this year - pro-rata calculation
      monthsWorked = 12 - entryDate.getMonth();
      if (entryDate.getDate() > 15) {
        monthsWorked--;
      }
    }

    // Base entitlement based on contract type
    let baseEntitlement = DEFAULT_VACATION_DAYS;

    if (contractType === ContractType.PART_TIME) {
      baseEntitlement = Math.round(DEFAULT_VACATION_DAYS * 0.8);
    } else if (contractType === ContractType.MINI_JOB) {
      baseEntitlement = Math.round(DEFAULT_VACATION_DAYS * 0.5);
    } else if (contractType === ContractType.INTERN || contractType === ContractType.WORKING_STUDENT) {
      baseEntitlement = Math.round(DEFAULT_VACATION_DAYS * 0.6);
    }

    // Calculate pro-rata entitlement
    return Math.round((baseEntitlement / 12) * monthsWorked);
  }

  /**
   * Check for overlapping absences for an employee
   */
  private async checkEmployeeConflicts(
    employeeId: string,
    startDate: Date,
    endDate: Date,
    excludeId?: string,
  ): Promise<ConflictResult> {
    const where: Prisma.AbsenceWhereInput = {
      employeeId,
      status: { not: ApprovalStatus.CANCELLED },
      OR: [
        {
          startDate: { lte: endDate },
          endDate: { gte: startDate },
        },
      ],
    };

    if (excludeId) {
      where.id = { not: excludeId };
    }

    const overlapping = await this.prisma.absence.findMany({
      where,
      include: {
        employee: {
          include: {
            user: {
              select: {
                firstName: true,
                lastName: true,
              },
            },
          },
        },
      },
    });

    return {
      hasConflicts: overlapping.length > 0,
      conflicts: overlapping.map((a) => ({
        employeeId: a.employeeId,
        employeeName: `${a.employee.user.firstName} ${a.employee.user.lastName}`,
        absenceId: a.id,
        type: a.type,
        startDate: a.startDate,
        endDate: a.endDate,
        overlapDays: this.calculateOverlapDays(startDate, endDate, a.startDate, a.endDate),
      })),
    };
  }

  /**
   * Check for team coverage conflicts in a department
   */
  private async checkDepartmentConflicts(
    departmentId: string,
    startDate: Date,
    endDate: Date,
    excludeId?: string,
  ): Promise<ConflictResult> {
    // Get total employees in department
    const totalEmployees = await this.prisma.user.count({
      where: {
        departmentId,
        isActive: true,
        employee: { isNot: null },
      },
    });

    // Get employees with approved absences during the period
    const where: Prisma.AbsenceWhereInput = {
      employee: {
        user: {
          departmentId,
        },
      },
      status: ApprovalStatus.APPROVED,
      OR: [
        {
          startDate: { lte: endDate },
          endDate: { gte: startDate },
        },
      ],
    };

    if (excludeId) {
      where.id = { not: excludeId };
    }

    const absences = await this.prisma.absence.findMany({
      where,
      include: {
        employee: {
          include: {
            user: {
              select: {
                firstName: true,
                lastName: true,
              },
            },
          },
        },
      },
    });

    const uniqueAbsentEmployees = new Set(absences.map((a) => a.employeeId));
    const absentCount = uniqueAbsentEmployees.size;
    const coveragePercentage =
      totalEmployees > 0
        ? Math.round(((totalEmployees - absentCount) / totalEmployees) * 100)
        : 100;

    return {
      hasConflicts: coveragePercentage < 50, // Alert if less than 50% coverage
      conflicts: absences.map((a) => ({
        employeeId: a.employeeId,
        employeeName: `${a.employee.user.firstName} ${a.employee.user.lastName}`,
        absenceId: a.id,
        type: a.type,
        startDate: a.startDate,
        endDate: a.endDate,
        overlapDays: this.calculateOverlapDays(startDate, endDate, a.startDate, a.endDate),
      })),
      teamCoverage: {
        totalEmployees,
        absentCount,
        coveragePercentage,
      },
    };
  }

  /**
   * Calculate overlap days between two date ranges
   */
  private calculateOverlapDays(
    start1: Date,
    end1: Date,
    start2: Date,
    end2: Date,
  ): number {
    const overlapStart = new Date(Math.max(start1.getTime(), start2.getTime()));
    const overlapEnd = new Date(Math.min(end1.getTime(), end2.getTime()));

    if (overlapStart > overlapEnd) {
      return 0;
    }

    return calculateWorkingDays(overlapStart, overlapEnd);
  }

  /**
   * Check if user should only see their own absences
   */
  private shouldFilterOwnOnly(currentUser: JwtPayload): boolean {
    const viewAllRoles = ['admin', 'hr-manager', 'team-lead'];
    return !currentUser.roles.some((r) => viewAllRoles.includes(r));
  }

  /**
   * Check if user can only view their department
   */
  private canViewDepartmentOnly(currentUser: JwtPayload): boolean {
    return (
      currentUser.roles.includes('team-lead') &&
      !currentUser.roles.includes('admin') &&
      !currentUser.roles.includes('hr-manager')
    );
  }

  /**
   * Check view permission for an absence
   */
  private async checkViewPermission(
    absence: { employeeId: string; employee: { user: { id: string; departmentId: string | null } } },
    currentUser: JwtPayload,
  ): Promise<void> {
    // Admins and HR managers can view all
    if (currentUser.roles.includes('admin') || currentUser.roles.includes('hr-manager')) {
      return;
    }

    // Own absence
    if (absence.employee.user.id === currentUser.sub) {
      return;
    }

    // Manager of the employee
    const isManager = await this.isManagerOfEmployee(currentUser.sub, absence.employeeId);
    if (isManager) {
      return;
    }

    // Team lead in same department
    if (currentUser.roles.includes('team-lead') && absence.employee.user.departmentId) {
      const user = await this.prisma.user.findUnique({
        where: { id: currentUser.sub },
        select: { departmentId: true },
      });
      if (user?.departmentId === absence.employee.user.departmentId) {
        return;
      }
    }

    throw new ForbiddenException('You do not have permission to view this absence');
  }

  /**
   * Check update permission for an absence
   */
  private async checkUpdatePermission(
    absence: { employeeId: string; employee: { user: { id: string } } },
    currentUser: JwtPayload,
  ): Promise<void> {
    // Admins and HR managers can update all
    if (currentUser.roles.includes('admin') || currentUser.roles.includes('hr-manager')) {
      return;
    }

    // Own absence
    if (absence.employee.user.id === currentUser.sub) {
      return;
    }

    throw new ForbiddenException('You can only update your own pending absences');
  }

  /**
   * Check cancel permission for an absence
   */
  private async checkCancelPermission(
    absence: { employeeId: string; employee: { user: { id: string } } },
    currentUser: JwtPayload,
  ): Promise<void> {
    // Admins and HR managers can cancel all
    if (currentUser.roles.includes('admin') || currentUser.roles.includes('hr-manager')) {
      return;
    }

    // Own absence
    if (absence.employee.user.id === currentUser.sub) {
      return;
    }

    throw new ForbiddenException('You can only cancel your own absences');
  }

  /**
   * Check approval permission for an absence
   */
  private async checkApprovalPermission(
    absence: { employeeId: string; employee: { user: { id: string; departmentId: string | null } } },
    currentUser: JwtPayload,
  ): Promise<void> {
    // Admins and HR managers can approve all
    if (currentUser.roles.includes('admin') || currentUser.roles.includes('hr-manager')) {
      return;
    }

    // Cannot approve own absences
    if (absence.employee.user.id === currentUser.sub) {
      throw new ForbiddenException('You cannot approve your own absences');
    }

    // Manager of the employee can approve
    const isManager = await this.isManagerOfEmployee(currentUser.sub, absence.employeeId);
    if (isManager) {
      return;
    }

    throw new ForbiddenException('You do not have permission to approve this absence');
  }

  /**
   * Queue an event for later notification processing
   */
  private queueEvent(event: AbsenceEvent): void {
    this.eventQueue.push(event);
    this.logger.debug(`Event queued: ${event.type} for absence ${event.payload.absenceId}`);
    // TODO: Connect to EventEmitter2 when available
  }

  /**
   * Get and clear pending events (for testing/integration)
   */
  getPendingEvents(): AbsenceEvent[] {
    const events = [...this.eventQueue];
    this.eventQueue = [];
    return events;
  }
}
