import prisma from '../../config/database';
import { AppError } from '../../shared/middleware/errorHandler';
import { AuditLogger } from '../../shared/utils/auditLogger';

class HRService {
  // ========== EMPLOYEES ==========
  
  private normalizeEmployeeData(data: any): Record<string, any> {
    const toStr = (v: any) => (v != null && String(v).trim()) ? String(v).trim() : undefined;
    const toDate = (v: any) => {
      if (!v || !String(v).trim()) return undefined;
      const d = new Date(v);
      return isNaN(d.getTime()) ? undefined : d;
    };
    const toNum = (v: any) => (v != null && v !== '') ? Number(v) : undefined;

    const raw: Record<string, any> = {
      firstName: toStr(data.firstName),
      lastName: toStr(data.lastName),
      firstNameAr: toStr(data.firstNameAr),
      lastNameAr: toStr(data.lastNameAr),
      email: toStr(data.email),
      phone: toStr(data.phone),
      mobile: toStr(data.mobile),
      dateOfBirth: toDate(data.dateOfBirth),
      gender: toStr(data.gender),
      nationality: toStr(data.nationality),
      nationalId: toStr(data.nationalId),
      employmentType: toStr(data.employmentType),
      contractType: toStr(data.contractType),
      hireDate: toDate(data.hireDate),
      departmentId: toStr(data.departmentId),
      positionId: toStr(data.positionId),
      reportingToId: toStr(data.reportingToId) || undefined,
      basicSalary: toNum(data.baseSalary ?? data.basicSalary) ?? 0,
    };
    return Object.fromEntries(Object.entries(raw).filter(([, v]) => v !== undefined));
  }

  async createEmployee(data: any, userId: string) {
    const uniqueEmployeeId = await this.generateEmployeeId();
    const payload = this.normalizeEmployeeData(data);

    if (!payload.firstName || !payload.lastName || !payload.email || !payload.mobile ||
        !payload.hireDate || !payload.departmentId || !payload.positionId) {
      throw new AppError(400, 'بيانات غير مكتملة - Missing required fields: firstName, lastName, email, mobile, hireDate, departmentId, positionId');
    }

    const employee = await prisma.employee.create({
      data: {
        uniqueEmployeeId,
        ...payload,
      } as any,
      include: {
        department: true,
        position: true,
      },
    });

    await AuditLogger.log({
      entityType: 'EMPLOYEE',
      entityId: employee.id,
      action: 'CREATE',
      userId,
    });

    return employee;
  }

  async findAllEmployees(filters: any, page: number, pageSize: number) {
    const skip = (page - 1) * pageSize;
    const where: any = {};

    if (filters.search) {
      where.OR = [
        { firstName: { contains: filters.search, mode: 'insensitive' } },
        { lastName: { contains: filters.search, mode: 'insensitive' } },
        { email: { contains: filters.search, mode: 'insensitive' } },
        { uniqueEmployeeId: { contains: filters.search } },
      ];
    }

    if (filters.departmentId) {
      where.departmentId = filters.departmentId;
    }

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

    const total = await prisma.employee.count({ where });

    const employees = await prisma.employee.findMany({
      where,
      skip,
      take: pageSize,
      include: {
        department: true,
        position: true,
        reportingTo: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            position: true,
          },
        },
      },
      orderBy: {
        hireDate: 'desc',
      },
    });

    return { employees, total, page, pageSize };
  }

  async findEmployeeById(id: string) {
    const employee = await prisma.employee.findUnique({
      where: { id },
      include: {
        department: true,
        position: {
          include: {
            permissions: true,
          },
        },
        reportingTo: true,
        directReports: true,
        user: {
          select: {
            id: true,
            email: true,
            username: true,
            isActive: true,
          },
        },
        attendances: {
          take: 30,
          orderBy: {
            date: 'desc',
          },
        },
        leaves: {
          take: 10,
          orderBy: {
            createdAt: 'desc',
          },
        },
        salaries: {
          take: 12,
          orderBy: {
            year: 'desc',
            month: 'desc',
          },
        },
      },
    });

    if (!employee) {
      throw new AppError(404, 'الموظف غير موجود - Employee not found');
    }

    return employee;
  }

  async updateEmployee(id: string, data: any, userId: string) {
    const existing = await prisma.employee.findUnique({ where: { id } });

    if (!existing) {
      throw new AppError(404, 'الموظف غير موجود - Employee not found');
    }

    const payload = this.normalizeEmployeeData(data);
    const employee = await prisma.employee.update({
      where: { id },
      data: payload,
      include: {
        department: true,
        position: true,
      },
    });

    await AuditLogger.log({
      entityType: 'EMPLOYEE',
      entityId: employee.id,
      action: 'UPDATE',
      userId,
      changes: {
        before: existing,
        after: employee,
      },
    });

    return employee;
  }

  async terminateEmployee(id: string, terminationDate: Date, reason: string, userId: string) {
    const employee = await prisma.employee.update({
      where: { id },
      data: {
        status: 'TERMINATED',
        terminationDate,
        terminationReason: reason,
      },
    });

    // Disable user account
    if (employee.id) {
      await prisma.user.updateMany({
        where: { employeeId: employee.id },
        data: { isActive: false },
      });
    }

    await AuditLogger.log({
      entityType: 'EMPLOYEE',
      entityId: employee.id,
      action: 'TERMINATE',
      userId,
      reason,
    });

    return employee;
  }

  // ========== ATTENDANCE ==========

  async recordAttendance(data: any, userId: string) {
    const attendance = await prisma.attendance.create({
      data: {
        ...data,
        sourceDeviceId: data.sourceDeviceId ?? undefined,
        externalId: data.externalId ?? undefined,
        rawData: data.rawData ?? undefined,
      },
    });
    return attendance;
  }

  async bulkSyncAttendanceFromDevice(deviceId: string, records: Array<{ employeePin: string; checkIn?: string; checkOut?: string; date: string }>, userId: string) {
    const results: { created: number; updated: number; skipped: number } = { created: 0, updated: 0, skipped: 0 };
    for (const rec of records) {
      const emp = await prisma.employee.findFirst({
        where: {
          OR: [
            { attendancePin: rec.employeePin },
            { uniqueEmployeeId: rec.employeePin },
          ],
        },
      });
      if (!emp) {
        results.skipped++;
        continue;
      }
      const date = new Date(rec.date);
      const existing = await prisma.attendance.findUnique({
        where: { employeeId_date: { employeeId: emp.id, date } },
      });
      const checkIn = rec.checkIn ? new Date(rec.checkIn) : null;
      const checkOut = rec.checkOut ? new Date(rec.checkOut) : null;
      const workHours = checkIn && checkOut ? (checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60) : null;

      if (existing) {
        await prisma.attendance.update({
          where: { id: existing.id },
          data: {
            checkIn: checkIn ?? existing.checkIn,
            checkOut: checkOut ?? existing.checkOut,
            workHours: workHours ?? existing.workHours,
            status: rec.checkIn ? 'PRESENT' : existing.status,
            sourceDeviceId: deviceId,
            externalId: `${deviceId}-${emp.id}-${rec.date}`,
            rawData: rec as any,
          },
        });
        results.updated++;
      } else {
        await prisma.attendance.create({
          data: {
            employeeId: emp.id,
            date,
            checkIn,
            checkOut,
            workHours,
            status: rec.checkIn ? 'PRESENT' : 'ABSENT',
            sourceDeviceId: deviceId,
            externalId: `${deviceId}-${emp.id}-${rec.date}`,
            rawData: rec as any,
          },
        });
        results.created++;
      }
    }
    await AuditLogger.log({ entityType: 'ATTENDANCE', entityId: deviceId, action: 'BULK_SYNC', userId, changes: results });
    return results;
  }

  async getAttendance(employeeId: string, month: number, year: number) {
    return prisma.attendance.findMany({
      where: {
        employeeId,
        date: {
          gte: new Date(year, month - 1, 1),
          lte: new Date(year, month, 0),
        },
      },
      orderBy: {
        date: 'asc',
      },
    });
  }

  // ========== LEAVES ==========

  async createLeaveRequest(data: any, userId: string) {
    const allowedLeaveTypes = ['ANNUAL', 'HOURLY'];
    const normalizedLeaveType = String(data.leaveType || '').toUpperCase();

    if (!allowedLeaveTypes.includes(normalizedLeaveType)) {
      throw new AppError(400, 'نوع الإجازة غير مدعوم - Only ANNUAL and HOURLY leave types are allowed');
    }

    const startDate = new Date(data.startDate);
    const endDate = new Date(data.endDate);

    if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
      throw new AppError(400, 'تاريخ أو وقت الإجازة غير صالح');
    }

    const isInvalidRange = normalizedLeaveType === 'HOURLY'
      ? endDate <= startDate
      : endDate < startDate;

    if (isInvalidRange) {
      throw new AppError(400, 'وقت/تاريخ النهاية يجب أن يكون بعد البداية');
    }

    const days = normalizedLeaveType === 'HOURLY'
      ? 0
      : this.calculateLeaveDays(startDate, endDate);

    const year = startDate.getFullYear();

    if (normalizedLeaveType !== 'HOURLY') {
      const ent = await prisma.leaveEntitlement.findUnique({
        where: {
          employeeId_year_leaveType: {
            employeeId: data.employeeId,
            year,
            leaveType: normalizedLeaveType,
          },
        },
      });

      if (ent) {
        const available = ent.totalDays + ent.carriedOver - ent.usedDays;
        if (days > available) {
          throw new AppError(400, `رصيد الإجازة غير كافٍ - Insufficient leave balance. Available: ${available}, Requested: ${days}`);
        }
      }
    }

    const leave = await prisma.leave.create({
      data: {
        employeeId: data.employeeId,
        leaveType: normalizedLeaveType,
        startDate,
        endDate,
        days,
        reason: data.reason || undefined,
      },
      include: {
        employee: true,
      },
    });

    await AuditLogger.log({
      entityType: 'LEAVE',
      entityId: leave.id,
      action: 'CREATE',
      userId,
    });

    return leave;
  }

  async approveLeave(id: string, approvedBy: string, userId: string) {
    const leave = await prisma.leave.update({
      where: { id },
      data: {
        status: 'APPROVED',
        approvedBy,
        approvedAt: new Date(),
        rejectedReason: null,
      },
      include: {
        employee: true,
      },
    });

    const year = new Date(leave.startDate).getFullYear();
    await this.updateLeaveEntitlementUsed(leave.employeeId, year, leave.leaveType, leave.days);

    await AuditLogger.log({
      entityType: 'LEAVE',
      entityId: leave.id,
      action: 'APPROVE',
      userId,
    });

    return leave;
  }

  async rejectLeave(id: string, rejectedReason: string, userId: string) {
    const leave = await prisma.leave.findUnique({ where: { id } });
    if (!leave) throw new AppError(404, 'طلب الإجازة غير موجود - Leave request not found');

    const updated = await prisma.leave.update({
      where: { id },
      data: {
        status: 'REJECTED',
        rejectedReason,
        approvedBy: null,
        approvedAt: null,
      },
      include: { employee: true },
    });

    await AuditLogger.log({
      entityType: 'LEAVE',
      entityId: id,
      action: 'REJECT',
      userId,
      reason: rejectedReason,
    });
    return updated;
  }

  async findAllLeaves(filters: { employeeId?: string; status?: string }, page: number, pageSize: number) {
    const skip = (page - 1) * pageSize;
    const where: any = {};
    if (filters.employeeId) where.employeeId = filters.employeeId;
    if (filters.status && filters.status !== 'all') where.status = filters.status;

    const [total, leaves] = await Promise.all([
      prisma.leave.count({ where }),
      prisma.leave.findMany({
        where,
        skip,
        take: pageSize,
        include: { employee: { select: { id: true, firstName: true, lastName: true, uniqueEmployeeId: true } } },
        orderBy: { createdAt: 'desc' },
      }),
    ]);
    return { leaves, total, page, pageSize };
  }


      private calculateLeaveHours(startDate: Date, endDate: Date) {
    const diffMs = new Date(endDate).getTime() - new Date(startDate).getTime();
    const diffHours = diffMs / (1000 * 60 * 60);
    return diffHours > 0 ? diffHours : 0;
  }

  private async canManagerApproveLeave(leave: any) {
    if (leave.leaveType === 'ANNUAL') {
      const yearStart = new Date(new Date(leave.startDate).getFullYear(), 0, 1);
      const yearEnd = new Date(new Date(leave.startDate).getFullYear(), 11, 31, 23, 59, 59, 999);

      const approvedAnnualLeaves = await prisma.leave.findMany({
        where: {
          employeeId: leave.employeeId,
          leaveType: 'ANNUAL',
          status: 'APPROVED',
          startDate: {
            gte: yearStart,
            lte: yearEnd,
          },
        },
        select: {
          id: true,
          days: true,
        },
      });

      const usedDays = approvedAnnualLeaves.reduce((sum, item) => sum + Number(item.days || 0), 0);
      return usedDays + Number(leave.days || 0) <= 12;
    }

    if (leave.leaveType === 'HOURLY') {
      const start = new Date(leave.startDate);
      const monthStart = new Date(start.getFullYear(), start.getMonth(), 1);
      const monthEnd = new Date(start.getFullYear(), start.getMonth() + 1, 0, 23, 59, 59, 999);

      const approvedHourlyLeaves = await prisma.leave.findMany({
        where: {
          employeeId: leave.employeeId,
          leaveType: 'HOURLY',
          status: 'APPROVED',
          startDate: {
            gte: monthStart,
            lte: monthEnd,
          },
        },
        select: {
          id: true,
          startDate: true,
          endDate: true,
        },
      });

      const usedHours = approvedHourlyLeaves.reduce((sum, item) => {
        return sum + this.calculateLeaveHours(item.startDate, item.endDate);
      }, 0);

      const requestedHours = this.calculateLeaveHours(leave.startDate, leave.endDate);
      return usedHours + requestedHours <= 3;
    }

    return false;
  }

async findManagedLeaves(status?: string) {
  const where: any = {};

  if (status && status !== 'all') {
    where.status = status;
  }

  return prisma.leave.findMany({
    where,
    include: {
      employee: {
        select: {
          id: true,
          firstName: true,
          lastName: true,
          uniqueEmployeeId: true,
          reportingToId: true,
        },
      },
    },
    orderBy: {
      createdAt: 'desc',
    },
    take: 100,
  });
}

  async managerApproveLeave(id: string, userId: string) {
  const leave = await prisma.leave.findUnique({
    where: { id },
    include: {
      employee: {
        select: {
          id: true,
          reportingToId: true,
        },
      },
    },
  });

  if (!leave) {
    throw new AppError(404, 'طلب الإجازة غير موجود - Leave request not found');
  }

  if (leave.status !== 'PENDING') {
    throw new AppError(400, 'لا يمكن اعتماد طلب غير معلق - Only pending leave can be approved');
  }

  const canApprove = await this.canManagerApproveLeave(leave);
  if (!canApprove) {
    throw new AppError(403, 'الطلب يتجاوز صلاحية مدير القسم ويحتاج موافقة HR - This leave exceeds manager approval limits and requires HR approval');
  }

  const updated = await prisma.leave.update({
    where: { id },
    data: {
      status: 'APPROVED',
      approvedBy: userId,
      approvedAt: new Date(),
      rejectedReason: null,
    },
    include: {
      employee: true,
    },
  });

  const year = new Date(updated.startDate).getFullYear();
  await this.updateLeaveEntitlementUsed(updated.employeeId, year, updated.leaveType, updated.days);

  await AuditLogger.log({
    entityType: 'LEAVE',
    entityId: updated.id,
    action: 'MANAGER_APPROVE',
    userId,
  });

  return updated;
}

  async managerRejectLeave(id: string, rejectedReason: string, userId: string) {
  const leave = await prisma.leave.findUnique({
    where: { id },
    include: {
      employee: {
        select: {
          id: true,
          reportingToId: true,
        },
      },
    },
  });

  if (!leave) {
    throw new AppError(404, 'طلب الإجازة غير موجود - Leave request not found');
  }

  if (leave.status !== 'PENDING') {
    throw new AppError(400, 'لا يمكن رفض طلب غير معلق - Only pending leave can be rejected');
  }

  const updated = await prisma.leave.update({
    where: { id },
    data: {
      status: 'REJECTED',
      rejectedReason,
      approvedBy: null,
      approvedAt: null,
    },
    include: {
      employee: true,
    },
  });

  await AuditLogger.log({
    entityType: 'LEAVE',
    entityId: updated.id,
    action: 'MANAGER_REJECT',
    userId,
    reason: rejectedReason,
  });

  return updated;
}

  private async updateLeaveEntitlementUsed(employeeId: string, year: number, leaveType: string, days: number) {
    const ent = await prisma.leaveEntitlement.findUnique({
      where: { employeeId_year_leaveType: { employeeId, year, leaveType } },
    });
    if (ent) {
      await prisma.leaveEntitlement.update({
        where: { employeeId_year_leaveType: { employeeId, year, leaveType } },
        data: { usedDays: { increment: days } },
      });
    }
  }

  // ========== SALARIES ==========

  async processSalary(employeeId: string, month: number, year: number, userId: string) {
    const employee = await prisma.employee.findUnique({
      where: { id: employeeId },
      include: {
        allowances: {
          where: {
            OR: [
              { isRecurring: true },
              {
                startDate: {
                  lte: new Date(year, month, 0),
                },
                OR: [
                  { endDate: null },
                  {
                    endDate: {
                      gte: new Date(year, month - 1, 1),
                    },
                  },
                ],
              },
            ],
          },
        },
        commissions: {
          where: {
            month,
            year,
            status: 'APPROVED',
          },
        },
      },
    });

    if (!employee) {
      throw new AppError(404, 'الموظف غير موجود - Employee not found');
    }

    const basicSalary = employee.basicSalary;
    const allowances = employee.allowances.reduce((sum, a) => sum + Number(a.amount), 0);
    const commissions = employee.commissions.reduce((sum, c) => sum + Number(c.amount), 0);
    
    // Calculate overtime from attendance
    const attendance = await prisma.attendance.findMany({
      where: {
        employeeId,
        date: {
          gte: new Date(year, month - 1, 1),
          lte: new Date(year, month, 0),
        },
      },
    });
    
    const overtimeHours = attendance.reduce((sum, a) => sum + Number(a.overtimeHours || 0), 0);
    const overtimePay = overtimeHours * 50; // SAR 50 per hour

    const deductions = 0; // Calculate based on business rules
    
    const netSalary = Number(basicSalary) + allowances + commissions + overtimePay - deductions;

    const salary = await prisma.salary.create({
      data: {
        employeeId,
        month,
        year,
        basicSalary,
        allowances,
        deductions,
        commissions,
        overtimePay,
        netSalary,
      },
    });

    await AuditLogger.log({
      entityType: 'SALARY',
      entityId: salary.id,
      action: 'PROCESS',
      userId,
    });

    return salary;
  }

  // ========== LOANS ==========

  private async generateLoanNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `LN-${year}-`;
    const last = await prisma.loan.findFirst({
      where: { loanNumber: { startsWith: prefix } },
      orderBy: { createdAt: 'desc' },
      select: { loanNumber: true },
    });
    let next = 1;
    if (last) {
      const parts = last.loanNumber.split('-');
      next = parseInt(parts[2] || '0') + 1;
    }
    return `${prefix}${next.toString().padStart(4, '0')}`;
  }

 async findAllLoans(filters: { employeeId?: string; status?: string }, page: number, pageSize: number) {
  const skip = (page - 1) * pageSize;
  const where: any = {};
  if (filters.employeeId) where.employeeId = filters.employeeId;
  if (filters.status && filters.status !== 'all') where.status = filters.status;

  const [total, loans] = await Promise.all([
    prisma.loan.count({ where }),
    prisma.loan.findMany({
      where,
      skip,
      take: pageSize,
      include: {
        employee: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            uniqueEmployeeId: true,
            basicSalary: true,
            position: {
              select: {
                code: true,
                title: true,
                titleAr: true,
              },
            },
          },
        },
        installmentsList: true,
      },
      orderBy: { createdAt: 'desc' },
    }),
  ]);

  return { loans, total, page, pageSize };
}

  async findLoanById(id: string) {
  const loan = await prisma.loan.findUnique({
    where: { id },
    include: {
      employee: {
        select: {
          id: true,
          firstName: true,
          lastName: true,
          uniqueEmployeeId: true,
          basicSalary: true,
          position: {
            select: {
              code: true,
              title: true,
              titleAr: true,
            },
          },
        },
      },
      installmentsList: { orderBy: { installmentNumber: 'asc' } },
    },
  });

  if (!loan) throw new AppError(404, 'القرض غير موجود - Loan not found');
  return loan;
}

private isSystemAdminUser(user: any) {
  const positionCode = user?.employee?.position?.code?.toUpperCase?.() || '';
  const positionTitle = user?.employee?.position?.title?.toUpperCase?.() || '';
  const positionTitleAr = user?.employee?.position?.titleAr || '';

  return (
    positionCode === 'SYS_ADMIN' ||
    positionCode === 'SYSTEM_ADMIN' ||
    positionTitle === 'SYSTEM ADMINISTRATOR' ||
    positionTitleAr === 'مدير النظام'
  );
}

  async createLoan(data: { employeeId: string; type: string; amount: number; installments?: number; reason?: string }, userId: string) {
  if (!data.reason || !data.reason.trim()) {
    throw new AppError(400, 'سبب القرض مطلوب - Loan reason is required');
  }

  if (!data.amount || Number(data.amount) <= 0) {
    throw new AppError(400, 'مبلغ القرض غير صالح - Invalid loan amount');
  }

  const loanNumber = await this.generateLoanNumber();
  const installments = data.installments || 1;
  const monthlyAmount = data.amount / installments;

  const loan = await prisma.loan.create({
    data: {
      loanNumber,
      employeeId: data.employeeId,
      type: data.type,
      amount: data.amount,
      installments,
      monthlyAmount,
      reason: data.reason.trim(),
      status: 'PENDING_HR',
    },
    include: { employee: true },
  });

  await AuditLogger.log({ entityType: 'LOAN', entityId: loan.id, action: 'CREATE', userId });
  return loan;
}

  async approveLoan(id: string, approvedBy: string, startDate: Date, userId: string) {
  const loan = await prisma.loan.findUnique({
    where: { id },
    include: {
      installmentsList: true,
      employee: {
        select: {
          id: true,
          basicSalary: true,
        },
      },
    },
  });

  if (!loan) {
    throw new AppError(404, 'القرض غير موجود - Loan not found');
  }

  if (!['PENDING_HR', 'PENDING_ADMIN'].includes(loan.status)) {
    throw new AppError(400, 'لا يمكن الموافقة على هذا القرض بهذه الحالة - Cannot approve this loan in current status');
  }

  const approverUser = await prisma.user.findUnique({
    where: { id: approvedBy },
    include: {
      employee: {
        include: {
          position: true,
        },
      },
    },
  });

  if (!approverUser) {
    throw new AppError(404, 'المستخدم غير موجود - User not found');
  }

  const isSystemAdmin = this.isSystemAdminUser(approverUser);
  const basicSalary = Number(loan.employee?.basicSalary || 0);
  const loanAmount = Number(loan.amount || 0);
  const needsAdminApproval = basicSalary > 0 && loanAmount > basicSalary * 0.5;

  // المرحلة الأولى: HR approval
  if (loan.status === 'PENDING_HR') {
    if (needsAdminApproval) {
      const updatedLoan = await prisma.loan.update({
        where: { id },
        data: {
          status: 'PENDING_ADMIN',
        },
      });

      await AuditLogger.log({
        entityType: 'LOAN',
        entityId: id,
        action: 'HR_APPROVE_FORWARD_TO_ADMIN',
        userId,
      });

      return updatedLoan;
    }
  }

  // المرحلة الثانية: Admin approval إذا تجاوز 50%
  if (loan.status === 'PENDING_ADMIN' && !isSystemAdmin) {
    throw new AppError(403, 'هذا الطلب يحتاج موافقة مدير النظام - System Administrator approval required');
  }

  const monthlyAmount = Number(loan.monthlyAmount || loan.amount) / loan.installments;
  const installments: { installmentNumber: number; dueDate: Date; amount: number }[] = [];
  let d = new Date(startDate);

  for (let i = 1; i <= loan.installments; i++) {
    installments.push({
      installmentNumber: i,
      dueDate: new Date(d),
      amount: monthlyAmount,
    });
    d.setMonth(d.getMonth() + 1);
  }

  const endDate = installments.length ? installments[installments.length - 1].dueDate : null;

  await prisma.$transaction([
    prisma.loan.update({
      where: { id },
      data: {
        status: 'ACTIVE',
        approvedBy,
        approvedAt: new Date(),
        startDate,
        endDate,
      },
    }),
    ...installments.map((inst) =>
      prisma.loanInstallment.create({
        data: {
          loanId: id,
          installmentNumber: inst.installmentNumber,
          dueDate: inst.dueDate,
          amount: inst.amount,
          status: 'PENDING',
        },
      })
    ),
  ]);

  await AuditLogger.log({
    entityType: 'LOAN',
    entityId: id,
    action: loan.status === 'PENDING_ADMIN' ? 'ADMIN_APPROVE' : 'APPROVE',
    userId,
  });

  return this.findLoanById(id);
}

  async rejectLoan(id: string, rejectedReason: string, userId: string) {
  const existing = await prisma.loan.findUnique({ where: { id } });

  if (!existing) {
    throw new AppError(404, 'القرض غير موجود - Loan not found');
  }

  if (!['PENDING_HR', 'PENDING_ADMIN'].includes(existing.status)) {
    throw new AppError(400, 'لا يمكن رفض هذا القرض بهذه الحالة - Cannot reject this loan in current status');
  }

  const loan = await prisma.loan.update({
    where: { id },
    data: { status: 'REJECTED', rejectedReason },
    include: { employee: true },
  });

  await AuditLogger.log({
    entityType: 'LOAN',
    entityId: id,
    action: 'REJECT',
    userId,
    reason: rejectedReason,
  });

  return loan;
}
  async recordLoanInstallmentPayment(loanId: string, installmentId: string, paidDate: Date, userId: string) {
    await prisma.loanInstallment.update({
      where: { id: installmentId },
      data: { status: 'PAID', paidDate },
    });
    const allPaid = (await prisma.loanInstallment.count({ where: { loanId, status: 'PENDING' } })) === 0;
    if (allPaid) {
      await prisma.loan.update({ where: { id: loanId }, data: { status: 'PAID_OFF' } });
    }
    await AuditLogger.log({ entityType: 'LOAN_INSTALLMENT', entityId: installmentId, action: 'PAY', userId });
    return this.findLoanById(loanId);
  }

  // ========== PURCHASE REQUESTS ==========

  private async generatePurchaseRequestNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `PR-${year}-`;
    const last = await prisma.purchaseRequest.findFirst({
      where: { requestNumber: { startsWith: prefix } },
      orderBy: { createdAt: 'desc' },
      select: { requestNumber: true },
    });
    let next = 1;
    if (last) {
      const parts = last.requestNumber.split('-');
      next = parseInt(parts[2] || '0') + 1;
    }
    return `${prefix}${next.toString().padStart(4, '0')}`;
  }

  async findAllPurchaseRequests(filters: { employeeId?: string; status?: string }, page: number, pageSize: number) {
    const skip = (page - 1) * pageSize;
    const where: any = {};
    if (filters.employeeId) where.employeeId = filters.employeeId;
    if (filters.status && filters.status !== 'all') where.status = filters.status;
    const [total, requests] = await Promise.all([
      prisma.purchaseRequest.count({ where }),
      prisma.purchaseRequest.findMany({
        where,
        skip,
        take: pageSize,
        include: { employee: { select: { id: true, firstName: true, lastName: true, uniqueEmployeeId: true } } },
        orderBy: { createdAt: 'desc' },
      }),
    ]);
    return { purchaseRequests: requests, total, page, pageSize };
  }

  async findPurchaseRequestById(id: string) {
    const req = await prisma.purchaseRequest.findUnique({ where: { id }, include: { employee: true } });
    if (!req) throw new AppError(404, 'طلب الشراء غير موجود - Purchase request not found');
    return req;
  }

  async createPurchaseRequest(data: { employeeId: string; items: any[]; reason?: string; priority?: string }, userId: string) {
    const requestNumber = await this.generatePurchaseRequestNumber();
    const totalAmount = Array.isArray(data.items)
      ? data.items.reduce((s: number, i: any) => s + (Number(i.estimatedPrice || 0) * Number(i.quantity || 1)), 0)
      : 0;
    const req = await prisma.purchaseRequest.create({
      data: {
        requestNumber,
        employeeId: data.employeeId,
        items: data.items,
        totalAmount,
        reason: data.reason,
        priority: data.priority || 'NORMAL',
        status: 'PENDING',
      },
      include: { employee: true },
    });
    await AuditLogger.log({ entityType: 'PURCHASE_REQUEST', entityId: req.id, action: 'CREATE', userId });
    return req;
  }

  async approvePurchaseRequest(id: string, approvedBy: string, userId: string) {
    const req = await prisma.purchaseRequest.update({
      where: { id },
      data: { status: 'APPROVED', approvedBy, approvedAt: new Date(), rejectedReason: null },
      include: { employee: true },
    });
    await AuditLogger.log({ entityType: 'PURCHASE_REQUEST', entityId: id, action: 'APPROVE', userId });
    return req;
  }

  async rejectPurchaseRequest(id: string, rejectedReason: string, userId: string) {
    const req = await prisma.purchaseRequest.update({
      where: { id },
      data: { status: 'REJECTED', rejectedReason },
      include: { employee: true },
    });
    await AuditLogger.log({ entityType: 'PURCHASE_REQUEST', entityId: id, action: 'REJECT', userId, reason: rejectedReason });
    return req;
  }

  // ========== LEAVE ENTITLEMENTS ==========

  async getLeaveBalance(employeeId: string, year: number) {
    const entitlements = await prisma.leaveEntitlement.findMany({
      where: { employeeId, year },
    });
    const approvedLeaves = await prisma.leave.findMany({
      where: { employeeId, status: 'APPROVED', startDate: { gte: new Date(year, 0, 1) }, endDate: { lte: new Date(year, 11, 31) } },
    });
    const usedByType: Record<string, number> = {};
    for (const l of approvedLeaves) {
      usedByType[l.leaveType] = (usedByType[l.leaveType] || 0) + l.days;
    }
    return entitlements.map((e) => ({
      leaveType: e.leaveType,
      totalDays: e.totalDays,
      carriedOver: e.carriedOver,
      usedDays: usedByType[e.leaveType] ?? e.usedDays,
      available: e.totalDays + e.carriedOver - (usedByType[e.leaveType] ?? e.usedDays),
    }));
  }

  async findAllLeaveEntitlements(employeeId?: string, year?: number) {
    const where: any = {};
    if (employeeId) where.employeeId = employeeId;
    if (year) where.year = year;
    return prisma.leaveEntitlement.findMany({
      where,
      include: { employee: { select: { id: true, firstName: true, lastName: true } } },
      orderBy: [{ employeeId: 'asc' }, { year: 'desc' }, { leaveType: 'asc' } ],
    });
  }

 async upsertLeaveEntitlement(data: { employeeId: string; year: number; leaveType: string; totalDays: number; carriedOver?: number; notes?: string }, userId: string) {
  const allowedLeaveTypes = ['ANNUAL', 'HOURLY'];
  const normalizedLeaveType = String(data.leaveType || '').toUpperCase();

  if (!allowedLeaveTypes.includes(normalizedLeaveType)) {
    throw new AppError(400, 'نوع رصيد الإجازة غير مدعوم - Only ANNUAL and HOURLY leave entitlement types are allowed');
  }

  const ent = await prisma.leaveEntitlement.upsert({
    where: {
      employeeId_year_leaveType: {
        employeeId: data.employeeId,
        year: data.year,
        leaveType: normalizedLeaveType,
      },
    },
    create: {
      employeeId: data.employeeId,
      year: data.year,
      leaveType: normalizedLeaveType,
      totalDays: data.totalDays,
      carriedOver: data.carriedOver || 0,
      notes: data.notes,
    },
    update: {
      totalDays: data.totalDays,
      carriedOver: data.carriedOver ?? undefined,
      notes: data.notes,
    },
  });

  await AuditLogger.log({ entityType: 'LEAVE_ENTITLEMENT', entityId: ent.id, action: 'UPSERT', userId });
  return ent;
}

  // ========== EMPLOYEE CONTRACTS ==========

  private async generateContractNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `ECT-${year}-`;
    const last = await prisma.employeeContract.findFirst({
      where: { contractNumber: { startsWith: prefix } },
      orderBy: { createdAt: 'desc' },
      select: { contractNumber: true },
    });
    let next = 1;
    if (last) {
      const parts = last.contractNumber.split('-');
      next = parseInt(parts[2] || '0') + 1;
    }
    return `${prefix}${next.toString().padStart(4, '0')}`;
  }

  async findAllEmployeeContracts(filters: { employeeId?: string; status?: string }, page: number, pageSize: number) {
    const skip = (page - 1) * pageSize;
    const where: any = {};
    if (filters.employeeId) where.employeeId = filters.employeeId;
    if (filters.status && filters.status !== 'all') where.status = filters.status;
    const [total, contracts] = await Promise.all([
      prisma.employeeContract.count({ where }),
      prisma.employeeContract.findMany({
        where,
        skip,
        take: pageSize,
        include: { employee: { select: { id: true, firstName: true, lastName: true, uniqueEmployeeId: true } } },
        orderBy: { startDate: 'desc' },
      }),
    ]);
    return { contracts, total, page, pageSize };
  }

  async findEmployeeContractById(id: string) {
    const c = await prisma.employeeContract.findUnique({ where: { id }, include: { employee: true } });
    if (!c) throw new AppError(404, 'العقد غير موجود - Contract not found');
    return c;
  }

  async createEmployeeContract(data: { employeeId: string; type: string; startDate: Date; endDate?: Date; salary: number; documentUrl?: string; notes?: string }, userId: string) {
    const contractNumber = await this.generateContractNumber();
    const contract = await prisma.employeeContract.create({
      data: {
        contractNumber,
        employeeId: data.employeeId,
        type: data.type,
        startDate: data.startDate,
        endDate: data.endDate,
        salary: data.salary,
        documentUrl: data.documentUrl,
        notes: data.notes,
        status: 'ACTIVE',
      },
      include: { employee: true },
    });
    await AuditLogger.log({ entityType: 'EMPLOYEE_CONTRACT', entityId: contract.id, action: 'CREATE', userId });
    return contract;
  }

  async updateEmployeeContract(id: string, data: { type?: string; endDate?: Date; salary?: number; documentUrl?: string; status?: string; notes?: string }, userId: string) {
    const contract = await prisma.employeeContract.update({
      where: { id },
      data,
      include: { employee: true },
    });
    await AuditLogger.log({ entityType: 'EMPLOYEE_CONTRACT', entityId: id, action: 'UPDATE', userId });
    return contract;
  }

  // ========== HELPERS ==========

  private async generateEmployeeId(): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `EMP-${year}-`;
    
    const lastEmployee = await prisma.employee.findFirst({
      where: {
        uniqueEmployeeId: {
          startsWith: prefix,
        },
      },
      orderBy: {
        createdAt: 'desc',
      },
      select: {
        uniqueEmployeeId: true,
      },
    });

    let nextNumber = 1;
    if (lastEmployee) {
      const lastNumber = parseInt(lastEmployee.uniqueEmployeeId.split('-')[2]);
      nextNumber = lastNumber + 1;
    }

    return `${prefix}${nextNumber.toString().padStart(4, '0')}`;
  }

  private calculateLeaveDays(startDate: Date, endDate: Date): number {
    const start = new Date(startDate);
    const end = new Date(endDate);
    const diffTime = Math.abs(end.getTime() - start.getTime());
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    return diffDays + 1;
  }

  // ========== DEPARTMENTS ==========
  
  async findAllDepartments() {
    const departments = await prisma.department.findMany({
      where: { isActive: true },
      include: {
        parent: { select: { id: true, name: true, nameAr: true } },
        _count: { select: { children: true, employees: true } }
      },
      orderBy: { name: 'asc' }
    });
    return departments;
  }

  async getDepartmentsHierarchy() {
    const departments = await prisma.department.findMany({
      where: { isActive: true },
      include: {
        parent: { select: { id: true, name: true, nameAr: true } },
        employees: { 
          where: { status: 'ACTIVE' },
          select: { id: true, firstName: true, lastName: true, firstNameAr: true, lastNameAr: true, position: { select: { title: true, titleAr: true } } }
        },
        positions: { select: { id: true, title: true, titleAr: true } },
        _count: { select: { children: true, employees: true } }
      },
      orderBy: { name: 'asc' }
    });
    const buildTree = (parentId: string | null): any[] =>
      departments
        .filter((d) => d.parentId === parentId)
        .map((d) => ({
          id: d.id,
          name: d.name,
          nameAr: d.nameAr,
          code: d.code,
          parentId: d.parentId,
          description: d.description,
          employees: d.employees,
          positions: d.positions,
          _count: d._count,
          children: buildTree(d.id)
        }));
    return buildTree(null);
  }

  async createDepartment(data: { name: string; nameAr?: string; code: string; parentId?: string; description?: string }, userId: string) {
    const existing = await prisma.department.findUnique({ where: { code: data.code } });
    if (existing) {
      throw new AppError(400, 'كود القسم مستخدم مسبقاً - Department code already exists');
    }
    if (data.parentId) {
      const parent = await prisma.department.findUnique({ where: { id: data.parentId } });
      if (!parent) {
        throw new AppError(400, 'القسم الأب غير موجود - Parent department not found');
      }
    }
    const department = await prisma.department.create({
      data: {
        name: data.name,
        nameAr: data.nameAr,
        code: data.code,
        parentId: data.parentId || null,
        description: data.description
      }
    });
    await AuditLogger.log({ entityType: 'DEPARTMENT', entityId: department.id, action: 'CREATE', userId, changes: { created: department } });
    return department;
  }

  async updateDepartment(id: string, data: { name?: string; nameAr?: string; code?: string; parentId?: string; description?: string; isActive?: boolean }, userId: string) {
    const existing = await prisma.department.findUnique({ where: { id } });
    if (!existing) {
      throw new AppError(404, 'القسم غير موجود - Department not found');
    }
    if (data.code && data.code !== existing.code) {
      const duplicate = await prisma.department.findUnique({ where: { code: data.code } });
      if (duplicate) {
        throw new AppError(400, 'كود القسم مستخدم مسبقاً - Department code already exists');
      }
    }
    if (data.parentId === id) {
      throw new AppError(400, 'لا يمكن تعيين القسم كأب لنفسه - Department cannot be its own parent');
    }
    const department = await prisma.department.update({
      where: { id },
      data: { name: data.name, nameAr: data.nameAr, code: data.code, parentId: data.parentId ?? undefined, description: data.description, isActive: data.isActive }
    });
    await AuditLogger.log({ entityType: 'DEPARTMENT', entityId: id, action: 'UPDATE', userId, changes: { before: existing, after: department } });
    return department;
  }

  async deleteDepartment(id: string, userId: string) {
    const dept = await prisma.department.findUnique({
      where: { id },
      include: { _count: { select: { children: true, employees: true } } }
    });
    if (!dept) {
      throw new AppError(404, 'القسم غير موجود - Department not found');
    }
    if (dept._count.children > 0) {
      throw new AppError(400, 'لا يمكن حذف قسم يحتوي على أقسام فرعية - Cannot delete department with sub-departments');
    }
    if (dept._count.employees > 0) {
      throw new AppError(400, 'لا يمكن حذف قسم فيه موظفون - Cannot delete department with employees');
    }
    await prisma.department.delete({ where: { id } });
    await AuditLogger.log({ entityType: 'DEPARTMENT', entityId: id, action: 'DELETE', userId });
    return { success: true };
  }

  // ========== POSITIONS ==========
  
  async findAllPositions() {
    const positions = await prisma.position.findMany({
      where: { isActive: true },
      include: {
        department: {
          select: {
            id: true,
            name: true,
            nameAr: true
          }
        }
      },
      orderBy: { title: 'asc' }
    });
    return positions;
  }
}

export const hrService = new HRService();

