import { Injectable } from "@nestjs/common"
import ExcelJS from "exceljs"
import puppeteer from "puppeteer"
import { CreatePayrollCycleDto } from "./dto/create-payroll-cycle.dto"
import { UpdatePayrollCycleDto } from "./dto/update-payroll-cycle.dto"
import { ProcessPayrollDto } from "./dto/process-payroll.dto"
import { PayrollFiltersDto } from "./dto/payroll-filters.dto"
import { ProcessPayrollFiltersDto } from "./dto/process-payroll-filters.dto"
import { PayrollCycleRepository } from "./repositories/payroll-cycle.repository"
import { PayrollEntryRepository } from "./repositories/payroll-entry.repository"
import { SalaryStructureRepository } from "./repositories/salary-structure.repository"
import { CompanySettingsRepository } from "../company/repositories/company-settings.repository"
import { CompanyProfileRepository } from "../company/repositories/company-profile.repository"
import { LeaveRequestRepository } from "../leave-requests/repositories/leave-request.repository"
import { EmployeeRepository } from "../employees/repositories/employee.repository"
import {
  LeaveRequestStatus,
  LeaveDayType,
} from "../leave-requests/entities/leave-request.entity"
import { EmployeeSalaryDetailRepository } from "../employees/repositories/employee-salary-detail.repository"
import { EmployeeSalaryHistoryRepository } from "../employees/repositories/employee-salary-history.repository"
import {
  errorMessage,
  isEmpty,
  successMessage,
  validationMessage,
} from "../../utils/helpers"
import {
  failureResponse,
  successResponse,
} from "../../common/response/response"
import { code } from "../../common/response/response.code"
import { messageKey } from "../../constants/message-keys"
import {
  PayrollCycle,
  PayrollCycleStatus,
} from "./entities/payroll-cycle.entity"
import { PayrollEntry } from "./entities/payroll-entry.entity"
import { verifyJwtToken } from "src/utils/jwt"
import { monthMap } from "src/common/constents/static-const"
import moment from "moment"

@Injectable()
export class PayrollService {
  constructor(
    private readonly payrollCycleRepository: PayrollCycleRepository,
    private readonly payrollEntryRepository: PayrollEntryRepository,
    private readonly salaryStructureRepository: SalaryStructureRepository,
    private readonly companySettingsRepository: CompanySettingsRepository,
    private readonly companyProfileRepository: CompanyProfileRepository,
    private readonly leaveRequestRepository: LeaveRequestRepository,
    private readonly employeeRepository: EmployeeRepository,
    private readonly employeeSalaryDetailRepository: EmployeeSalaryDetailRepository,
    private readonly employeeSalaryHistoryRepository: EmployeeSalaryHistoryRepository,
  ) {}

  async createPayrollCycle(
    createPayrollCycleDto: CreatePayrollCycleDto,
    token?: string,
    systemCompanyId?: number,
    systemUserId?: number,
  ) {
    let decoded: any = null

    // Handle system operations (from cron) vs user operations
    if (token) {
      decoded = verifyJwtToken(token)
      if (!decoded) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.invalid_token),
        )
      }
    } else if (systemCompanyId && systemUserId) {
      // System operation from cron
      decoded = {
        company_id: systemCompanyId,
        user_id: systemUserId,
      }
    } else {
      return failureResponse(
        code.VALIDATION,
        validationMessage("Either token or system parameters required"),
      )
    }

    // Check if payroll cycle already exists for the month/year
    const existingCycle = await this.payrollCycleRepository.getByParams({
      where: {
        company_id: decoded.company_id,
        month: createPayrollCycleDto.month,
        year: createPayrollCycleDto.year,
      },
      findOne: true,
    })

    if (existingCycle) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.already_exist, {
          ":data": "Payroll cycle",
          ":field": "month/year combination",
        }),
      )
    }

    let payrollCycle: any = new PayrollCycle()
    payrollCycle = {
      ...createPayrollCycleDto,
      status: PayrollCycleStatus.DRAFT,
      company_id: decoded.company_id,
      created_by: decoded.user_id,
    }

    await this.payrollCycleRepository.save(payrollCycle)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Payroll cycle" }),
    )
  }

  async findAllPayrollCycles(query: PayrollFiltersDto = {}, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10, search, month, year, status } = query

    const skip = (page - 1) * limit
    const take = parseInt(limit.toString())

    const whereConditions: any = {}

    // Add filters
    if (decoded.company_id) {
      whereConditions.company_id = decoded.company_id
    }

    if (month !== undefined && month !== null) {
      whereConditions.month = month
    }

    if (year !== undefined && year !== null) {
      whereConditions.year = year
    }

    if (status !== undefined && status !== null) {
      whereConditions.status = status
    }

    // Handle search (same logic as salary slip)
    // Handle search
    if (search) {
      const searchLower = search.toString().toLowerCase()

      // Case 1 → Numeric search (month or year)
      if (!isNaN(Number(searchLower))) {
        const num = Number(searchLower)

        // Month number
        if (num >= 1 && num <= 12) {
          whereConditions.month = num
        }

        // Year number
        if (num >= 1900 && num <= 2100) {
          whereConditions.year = num
        }
      }

      // Case 2 → Text month name (partial or full)
      const matchedMonth = Object.keys(monthMap).find((m) =>
        m.toLowerCase().startsWith(searchLower),
      )

      if (matchedMonth) {
        whereConditions.month = monthMap[matchedMonth]
      }
    }

    const payrollCycles: any = await this.payrollCycleRepository.getByParams({
      where: whereConditions,
      relations: ["company:id,name", "payroll_entries:id"],
      orderBy: { created_at: "DESC" },
      take,
      skip,
    })

    if (payrollCycles.data) {
      for (const cycle of payrollCycles.data) {
        // Calculate payroll month end date for joining date filter
        const payrollMonthEnd = moment
          .utc()
          .year(cycle.year)
          .month(cycle.month - 1)
          .endOf("month")
          .toDate()

        // Get all eligible employees for this payroll cycle
        const eligibleEmployees: any =
          await this.employeeRepository.getByParams({
            where: {
              company_id: decoded.company_id,
              status: 1,
              joining_date: { lte: payrollMonthEnd },
            },
            select: ["id", "net_payable_salary"],
          })

        // Get payroll entries for this cycle
        const cycleStats: any = await this.payrollEntryRepository.getByParams({
          where: { payroll_cycle_id: cycle.id },
          select: ["employee_id", "net_payable"],
        })

        // Create a map of payroll entries by employee_id
        const payrollEntryMap = new Map()
        if (cycleStats && cycleStats.length > 0) {
          cycleStats.forEach((entry: any) => {
            payrollEntryMap.set(entry.employee_id, entry.net_payable)
          })
        }

        // Get company settings for working days calculation
        const companySettings: any =
          await this.companySettingsRepository.getByParams({
            where: { company_id: decoded.company_id },
            findOne: true,
          })
        const defaultWorkingDays = companySettings?.working_days_per_month || 22

        // Calculate total amount: use payroll entry net_payable if exists, otherwise calculate properly
        let totalAmount = 0
        let employeeCount = 0

        for (const employee of eligibleEmployees) {
          employeeCount++
          if (payrollEntryMap.has(employee.id)) {
            // Use calculated net_payable from payroll entry (includes all adjustments)
            totalAmount += parseFloat(payrollEntryMap.get(employee.id) || 0)
          } else {
            // Calculate proper salary with deductions and unpaid leave for employees without entries
            try {
              // Get full employee details
              const fullEmployee: any =
                await this.employeeRepository.getByParams({
                  where: { id: employee.id },
                  findOne: true,
                })

              // Calculate attendance for this employee and month
              const attendanceData = await this.calculateEmployeeAttendance(
                employee.id,
                decoded.company_id,
                cycle.month,
                cycle.year,
                defaultWorkingDays,
              )

              // Get employee salary settings (dynamic components)
              const employeeSalaryDetails: any =
                await this.employeeSalaryDetailRepository.getByParams({
                  where: { employee_id: employee.id },
                  relations: ["salarySetting:id,name,type,type_value,value"],
                })

              // Calculate salary components using the same method as getEmployeesWithCalculatedSalary
              const salaryComponents =
                await this.calculateEmployeeSalaryComponents(
                  fullEmployee,
                  employeeSalaryDetails,
                  cycle.year,
                  cycle.month,
                )

              // Calculate unpaid leave deduction (same logic as getEmployeesWithCalculatedSalary)
              const grossSalary =
                fullEmployee.gross_salary ||
                fullEmployee.net_payable_salary ||
                salaryComponents.total_earnings
              const unpaidLeaveDeduction =
                attendanceData.working_days > 0 &&
                attendanceData.unpaid_leaves > 0
                  ? (parseFloat(grossSalary) / attendanceData.working_days) *
                    attendanceData.unpaid_leaves
                  : 0

              // Calculate final amounts (same logic as getEmployeesWithCalculatedSalary)
              const proRatedTotalEarnings = Number(
                salaryComponents.total_earnings,
              )
              const proRatedTotalDeductions = Number(
                salaryComponents.total_deductions,
              )
              const finalTotalDeductions =
                proRatedTotalDeductions + unpaidLeaveDeduction
              const finalNetPayable =
                proRatedTotalEarnings - finalTotalDeductions

              totalAmount += finalNetPayable
            } catch (error) {
              console.error(
                `Error calculating salary for employee ${employee.id}:`,
                error,
              )
              // Fallback to base salary if calculation fails
              totalAmount += parseFloat(employee.net_payable_salary || 0)
            }
          }
        }

        cycle.employees_count = employeeCount
        cycle.total_amount = totalAmount

        // Month Name
        const monthNames = [
          "January",
          "February",
          "March",
          "April",
          "May",
          "June",
          "July",
          "August",
          "September",
          "October",
          "November",
          "December",
        ]
        cycle.month_name = monthNames[cycle.month - 1]
      }
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Payroll cycles" }),
      payrollCycles,
    )
  }

  async findOnePayrollCycle(id: number) {
    const payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: { id },
      whereNull: ["deleted_at"],
      relations: [
        "company:id,name",
        "payroll_entries:id,employee_id,net_payable",
      ],
      findOne: true,
    })

    if (isEmpty(payrollCycle)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll cycle" }),
      )
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Payroll cycle" }),
      payrollCycle,
    )
  }

  async updatePayrollCycle(
    id: number,
    updatePayrollCycleDto: UpdatePayrollCycleDto,
    token: string,
  ) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: { id, company_id: decoded.company_id },
      findOne: true,
    })

    if (isEmpty(payrollCycle)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll cycle" }),
      )
    }

    // Check if payroll is locked (finalized or paid)
    if (payrollCycle.status !== PayrollCycleStatus.DRAFT) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(
          "Payroll cycle cannot be updated as it is already finalized or paid",
        ),
      )
    }

    // Check for duplicate month/year if updating
    if (updatePayrollCycleDto.month || updatePayrollCycleDto.year) {
      const existingCycle = await this.payrollCycleRepository.getByParams({
        where: {
          company_id: decoded.company_id,
          month: updatePayrollCycleDto.month || payrollCycle.month,
          year: updatePayrollCycleDto.year || payrollCycle.year,
        },
        whereNotIn: { id: [id] },
        findOne: true,
      })

      if (existingCycle) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.already_exist, {
            ":data": "Payroll cycle",
            ":field": "month/year combination",
          }),
        )
      }
    }

    // Update the payroll cycle
    Object.assign(payrollCycle, updatePayrollCycleDto)
    payrollCycle.updated_by = decoded.user_id
    await this.payrollCycleRepository.save(payrollCycle)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, { ":data": "Payroll cycle" }),
    )
  }

  async removePayrollCycle(id: number, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: { id, company_id: decoded.company_id },
      whereNull: ["deleted_at"],
      findOne: true,
    })

    if (isEmpty(payrollCycle)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll cycle" }),
      )
    }

    // Check if payroll has entries
    const hasEntries = await this.payrollEntryRepository.getByParams({
      where: { payroll_cycle_id: id },
      findOne: true,
    })

    if (hasEntries) {
      return failureResponse(
        code.VALIDATION,
        validationMessage("Cannot delete payroll cycle with existing entries"),
      )
    }

    await this.payrollCycleRepository.remove({ id: payrollCycle.id })

    await this.payrollCycleRepository.save({
      id: payrollCycle.id,
      deleted_by: decoded.user_id,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_removed, { ":data": "Payroll cycle" }),
    )
  }

  async processPayroll(processPayrollDto: ProcessPayrollDto, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { month, year, department_id } = processPayrollDto

    // Check if payroll cycle exists
    let payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: {
        company_id: decoded.company_id,
        month,
        year,
      },
      findOne: true,
    })

    // Create payroll cycle if it doesn't exist
    if (!payrollCycle) {
      const newCycle = new PayrollCycle()
      Object.assign(newCycle, {
        company_id: decoded.company_id,
        month,
        year,
        status: PayrollCycleStatus.DRAFT,
        created_by: decoded.user_id,
      })
      payrollCycle = await this.payrollCycleRepository.save(newCycle)
    }

    // Check if payroll is locked
    if (payrollCycle.status !== PayrollCycleStatus.DRAFT) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(
          "Payroll cycle is already finalized or paid and cannot be processed",
        ),
      )
    }

    // Get company settings for working days
    const companySettings: any =
      await this.companySettingsRepository.getByParams({
        where: { company_id: decoded.company_id },
        findOne: true,
      })

    const defaultWorkingDays = companySettings?.working_days_per_month || 22

    // Get employees based on criteria
    const whereConditions: any = {
      company_id: decoded.company_id,
      status: 1, // Active employees
    }

    if (department_id) {
      whereConditions.department_id = department_id
    }

    whereConditions.joining_date = {
      lte: moment
        .utc()
        .year(payrollCycle.year)
        .month(payrollCycle.month - 1)
        .endOf("month")
        .toDate(),
    }

    // Get employees with basic salary from employee table
    const employees: any = await this.employeeRepository.getByParams({
      where: whereConditions,
      relations: ["department:id,name"],
    })

    if (!employees || employees.length === 0) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(
          "No active employees found for the specified criteria",
        ),
      )
    }

    let processedCount = 0
    const errors = []

    // Process each employee
    for (const employee of employees) {
      try {
        // Check if entry already exists for this employee in this cycle
        const existingEntry = await this.payrollEntryRepository.getByParams({
          where: {
            payroll_cycle_id: payrollCycle.id,
            employee_id: employee.id,
          },
          findOne: true,
        })

        // Calculate attendance for this employee and month
        const attendanceData = await this.calculateEmployeeAttendance(
          employee.id,
          decoded.company_id,
          month,
          year,
          defaultWorkingDays,
        )

        // Get employee salary details for proper calculation
        const employeeSalaryDetails: any =
          await this.employeeSalaryDetailRepository.getByParams({
            where: { employee_id: employee.id },
            relations: ["salarySetting:id,name,type,type_value,value"],
          })

        // Calculate salary components
        const salaryComponents = await this.calculateEmployeeSalaryComponents(
          employee,
          employeeSalaryDetails,
          year,
          month,
        )

        // Add TDS as a deduction to salary components
        this.addTdsToSalaryComponents(employee, salaryComponents)

        const proRatedTotalEarnings = Number(salaryComponents.total_earnings)

        const proRatedTotalDeductions = Number(
          salaryComponents.total_deductions,
        )

        // Calculate unpaid leave deduction
        const grossSalary =
          employee.gross_salary ||
          employee.net_payable_salary ||
          salaryComponents.total_earnings
        const unpaidLeaveDeduction =
          attendanceData.working_days > 0 && attendanceData.unpaid_leaves > 0
            ? (parseFloat(grossSalary) / attendanceData.working_days) *
              attendanceData.unpaid_leaves
            : 0

        // Add unpaid leave deduction to total deductions
        const finalTotalDeductions =
          proRatedTotalDeductions + unpaidLeaveDeduction

        const payrollData = {
          company_id: decoded.company_id,
          payroll_cycle_id: payrollCycle.id,
          employee_id: employee.id,
          working_days: attendanceData.working_days,
          present_days: attendanceData.present_days,
          paid_leaves: attendanceData.paid_leaves,
          unpaid_leaves: attendanceData.unpaid_leaves,
          total_earnings: proRatedTotalEarnings,
          total_deductions: finalTotalDeductions,
          net_payable: proRatedTotalEarnings - finalTotalDeductions,
          updated_by: decoded.user_id,
        }

        if (!existingEntry) {
          // Create new payroll entry
          const payrollEntry = new PayrollEntry()
          Object.assign(payrollEntry, {
            ...payrollData,
            created_by: decoded.user_id,
          })
          await this.payrollEntryRepository.save(payrollEntry)
        }

        processedCount++
      } catch (error) {
        errors.push({
          employee_id: employee.id,
          error: error.message,
        })
      }
    }

    // Update status to finalized
    payrollCycle.status = PayrollCycleStatus.PAID
    payrollCycle.locked_by = decoded.user_id
    payrollCycle.locked_at = new Date()
    payrollCycle.updated_by = decoded.user_id

    await this.payrollCycleRepository.save(payrollCycle)

    return successResponse(
      code.SUCCESS,
      `Payroll processed successfully. ${processedCount} employees processed.`,
      {
        processed_count: processedCount,
        errors: errors,
        payroll_cycle_id: payrollCycle.id,
      } as any,
    )
  }

  async getProcessPayrollData(
    payrollCycleId: number,
    query: ProcessPayrollFiltersDto = {},
    token: string,
  ) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10 } = query

    const skip = (page - 1) * limit
    const take = parseInt(limit.toString())

    // First, check the payroll cycle status
    const payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: {
        id: payrollCycleId,
        company_id: decoded.company_id,
      },
      findOne: true,
    })

    if (!payrollCycle) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll cycle" }),
      )
    }

    // Check if payroll cycle is finalized/paid (not draft)
    const isProcessedCycle = payrollCycle.status !== PayrollCycleStatus.DRAFT

    if (isProcessedCycle) {
      // Cycle is finalized/paid - check if entries exist and return processed data
      const existingEntries: any =
        await this.payrollEntryRepository.getByParams({
          where: {
            payroll_cycle_id: payrollCycleId,
            company_id: decoded.company_id,
          },
          findOne: true,
        })

      if (existingEntries) {
        // Return processed entries in the same format
        return await this.getProcessedPayrollEntries(
          payrollCycleId,
          decoded.company_id,
          query,
          skip,
          take,
          payrollCycle,
        )
      }
    }

    // Cycle is draft OR no entries exist - show calculated salary components
    return await this.getEmployeesWithCalculatedSalary(
      decoded.company_id,
      query,
      skip,
      take,
      payrollCycle,
    )
  }

  /**
   * Get processed payroll entries in consistent format
   */
  private async getProcessedPayrollEntries(
    payrollCycleId: number,
    companyId: number,
    query: ProcessPayrollFiltersDto,
    skip: number,
    take: number,
    payrollCycle: any,
  ) {
    const { search, department_id, employee_id } = query

    const whereConditions: any = {
      payroll_cycle_id: payrollCycleId,
      company_id: companyId,
    }

    if (department_id) {
      whereConditions.department_id = department_id
    }

    if (employee_id) {
      whereConditions.employee_id = employee_id
    }

    // Add joining date filter through employee relation
    whereConditions["entity_employee.joining_date"] = {
      lte: moment
        .utc()
        .year(payrollCycle.year)
        .month(payrollCycle.month - 1)
        .endOf("month")
        .toDate(),
    }

    const searchConditions: any = {}
    if (search) {
      searchConditions.first_name = search
      searchConditions.last_name = search
    }

    const payrollEntries: any = await this.payrollEntryRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      relations: [
        "employee:id,first_name,last_name,email,department_id,gross_salary,basic_salary,joining_date.department:id,name",
      ],
      orderBy: { created_at: "DESC" },
      take,
      skip,
    })

    const processedEmployees = []

    if (payrollEntries.data && payrollEntries.data.length > 0) {
      for (const entry of payrollEntries.data) {
        // Get employee salary details for processed entries
        const employeeSalaryDetails: any =
          await this.employeeSalaryDetailRepository.getByParams({
            where: { employee_id: entry.employee_id },
            relations: ["salarySetting:id,name,type,type_value,value"],
          })

        // Calculate salary components for display
        const salaryComponents = await this.calculateEmployeeSalaryComponents(
          entry.employee,
          employeeSalaryDetails,
          payrollCycle.year,
          payrollCycle.month,
        )

        // Add TDS as a deduction to salary components
        this.addTdsToSalaryComponents(entry.employee, salaryComponents)

        const processedEmployee = {
          id: entry.id,
          employee_id: entry.employee_id,
          first_name: entry.employee.first_name,
          last_name: entry.employee.last_name,
          email: entry.employee.email,
          department: entry.employee.department,
          basic: parseFloat(entry.employee.basic_salary) || 0,
          gross_salary: parseFloat(entry.employee.gross_salary) || 0,
          working_days: entry.working_days,
          present_days: entry.present_days,
          paid_leaves: entry.paid_leaves,
          unpaid_leaves: entry.unpaid_leaves,
          salary_components: salaryComponents.dynamic_components,
          total_earnings: entry.total_earnings,
          total_deductions: entry.total_deductions,
          net_payable: entry.net_payable,
          payroll_cycle: {
            id: payrollCycle.id,
            month: payrollCycle.month,
            year: payrollCycle.year,
            status: payrollCycle.status,
          },
          is_processed: true,
        }

        processedEmployees.push(processedEmployee)
      }
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Payroll entries" }),
      {
        data: processedEmployees,
        total: payrollEntries.count || 0,
        page: Math.floor(skip / take) + 1,
        limit: take,
      } as any,
    )
  }

  /**
   * Get employees with calculated salary components (before payroll processing)
   */
  private async getEmployeesWithCalculatedSalary(
    companyId: number,
    query: ProcessPayrollFiltersDto,
    skip: number,
    take: number,
    payrollCycle: any,
  ) {
    const { search, department_id, employee_id } = query

    // Get company settings for working days
    const companySettings: any =
      await this.companySettingsRepository.getByParams({
        where: { company_id: companyId },
        findOne: true,
      })

    const defaultWorkingDays = companySettings?.working_days_per_month || 22

    // Get employees based on criteria
    const whereConditions: any = {
      company_id: companyId,
      status: 1, // Active employees
    }

    if (department_id) {
      whereConditions.department_id = department_id
    }

    if (employee_id) {
      whereConditions.id = employee_id
    }

    const searchConditions: any = {}
    if (search) {
      searchConditions.first_name = search
      searchConditions.last_name = search
    }

    whereConditions.joining_date = {
      lte: moment
        .utc()
        .year(payrollCycle.year)
        .month(payrollCycle.month - 1)
        .endOf("month")
        .toDate(),
    }

    const employees: any = await this.employeeRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      relations: ["department:id,name"],
      orderBy: { created_at: "DESC" },
      take,
      skip,
    })

    if (!employees.data || employees.data.length === 0) {
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Employees" }),
        { data: [], total: 0, page: 1, limit: take } as any,
      )
    }

    // Calculate salary components for each employee
    const processedEmployees = []

    for (const employee of employees.data) {
      try {
        // Check if payroll entry already exists for this employee in this cycle
        const existingPayrollEntry: any =
          await this.payrollEntryRepository.getByParams({
            where: {
              employee_id: employee.id,
              payroll_cycle_id: payrollCycle.id,
              company_id: companyId,
            },
            relations: [
              "employee:id,basic_salary,net_payable_salary,gross_salary",
            ],
            findOne: true,
          })

        if (existingPayrollEntry) {
          // Return saved payroll entry data
          // Get employee salary settings for dynamic components display
          const employeeSalaryDetails: any =
            await this.employeeSalaryDetailRepository.getByParams({
              where: { employee_id: employee.id },
              relations: ["salarySetting:id,name,type,type_value,value"],
            })

          // Calculate salary components for dynamic components display
          const salaryComponents = await this.calculateEmployeeSalaryComponents(
            employee,
            employeeSalaryDetails,
            payrollCycle.year,
            payrollCycle.month,
          )

          // Add TDS as a deduction to salary components
          this.addTdsToSalaryComponents(employee, salaryComponents)

          const processedEmployee = {
            employee_id: employee.id,
            first_name: employee.first_name,
            last_name: employee.last_name,
            email: employee.email,
            department: employee.department,
            // Salary components
            basic: parseFloat(employee.basic_salary) || 0,
            // Attendance from saved entry
            working_days: existingPayrollEntry.working_days,
            present_days: existingPayrollEntry.present_days,
            paid_leaves: existingPayrollEntry.paid_leaves,
            unpaid_leaves: existingPayrollEntry.unpaid_leaves,
            // Dynamic salary components
            salary_components: salaryComponents.dynamic_components,
            // Totals from saved entry
            total_earnings: existingPayrollEntry.total_earnings,
            total_deductions: existingPayrollEntry.total_deductions,
            unpaid_leave_deduction:
              existingPayrollEntry.total_deductions -
              Number(salaryComponents.total_deductions),
            net_payable: existingPayrollEntry.net_payable,
            // Metadata
            payroll_cycle: {
              id: payrollCycle.id,
              month: payrollCycle.month,
              year: payrollCycle.year,
              status: payrollCycle.status,
            },
            is_processed: true, // This is saved data from payroll entries
          }

          processedEmployees.push(processedEmployee)
        } else {
          // No existing entry - calculate as before
          // Calculate attendance for this employee and month
          const attendanceData = await this.calculateEmployeeAttendance(
            employee.id,
            companyId,
            payrollCycle.month,
            payrollCycle.year,
            defaultWorkingDays,
          )

          // Get employee salary settings (dynamic components)
          const employeeSalaryDetails: any =
            await this.employeeSalaryDetailRepository.getByParams({
              where: { employee_id: employee.id },
              relations: ["salarySetting:id,name,type,type_value,value"],
            })

          // Calculate salary components
          const salaryComponents = await this.calculateEmployeeSalaryComponents(
            employee,
            employeeSalaryDetails,
            payrollCycle.year,
            payrollCycle.month,
          )

          // Add TDS as a deduction to salary components
          this.addTdsToSalaryComponents(employee, salaryComponents)

          // Calculate unpaid leave deduction (same logic as updatePayrollAttendance)
          const grossSalary =
            employee.gross_salary ||
            employee.net_payable_salary ||
            salaryComponents.total_earnings
          const unpaidLeaveDeduction =
            attendanceData.working_days > 0 && attendanceData.unpaid_leaves > 0
              ? (parseFloat(grossSalary) / attendanceData.working_days) *
                attendanceData.unpaid_leaves
              : 0

          // Calculate final amounts (same logic as updatePayrollAttendance)
          const proRatedTotalEarnings = Number(salaryComponents.total_earnings)
          const proRatedTotalDeductions = Number(
            salaryComponents.total_deductions,
          )
          const finalTotalDeductions =
            proRatedTotalDeductions + unpaidLeaveDeduction
          const finalNetPayable = proRatedTotalEarnings - finalTotalDeductions

          const processedEmployee = {
            employee_id: employee.id,
            first_name: employee.first_name,
            last_name: employee.last_name,
            email: employee.email,
            department: employee.department,
            // Salary components
            basic: parseFloat(employee.basic_salary) || 0,
            // Attendance
            working_days: attendanceData.working_days,
            present_days: attendanceData.present_days,
            paid_leaves: attendanceData.paid_leaves,
            unpaid_leaves: attendanceData.unpaid_leaves,
            // Dynamic salary components
            salary_components: salaryComponents.dynamic_components,
            // Totals
            total_earnings: proRatedTotalEarnings,
            total_deductions: finalTotalDeductions,
            unpaid_leave_deduction: unpaidLeaveDeduction,
            net_payable: finalNetPayable,
            // Metadata
            payroll_cycle: {
              id: payrollCycle.id,
              month: payrollCycle.month,
              year: payrollCycle.year,
              status: payrollCycle.status,
            },
            is_processed: false, // This is calculated data, not processed entries
          }

          processedEmployees.push(processedEmployee)
        }
      } catch (error) {
        console.error(`Error processing employee ${employee.id}:`, error)
        // Continue with next employee
      }
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Employees" }),
      {
        data: processedEmployees,
        total: employees.count || 0,
        page: Math.floor(skip / take) + 1,
        limit: take,
      } as any,
    )
  }

  /**
   * Get basic salary from employee salary history based on payroll month
   */
  private async getBasicSalaryFromHistory(
    employeeId: number,
    companyId: number,
    payrollYear: number,
    payrollMonth: number,
  ): Promise<number> {
    try {
      // Create payroll month start date (e.g., 1/11/2025 for November 2025)
      const payrollMonthStart = new Date(payrollYear, payrollMonth - 1, 1)

      // Find salary history record where from_date <= payrollMonthStart and (to_date is null or to_date >= payrollMonthStart)
      // We'll use a more specific query approach
      const salaryHistories: any =
        await this.employeeSalaryHistoryRepository.getByParams({
          where: {
            employee_id: employeeId,
            company_id: companyId,
            from_date: { lte: payrollMonthStart },
          },
          orderBy: { from_date: "DESC" },
        })

      // Filter for active salary history (to_date is null or >= payrollMonthStart)
      let salaryHistory = null
      if (salaryHistories && salaryHistories.length > 0) {
        salaryHistory = salaryHistories.find(
          (history: any) =>
            !history.to_date || new Date(history.to_date) >= payrollMonthStart,
        )
      }

      if (salaryHistory && salaryHistory.basic_salary) {
        return parseFloat(salaryHistory.basic_salary) || 0
      }

      return 0
    } catch (error) {
      return 0
    }
  }

  /**
   * Calculate salary components for an employee based on salary settings
   */
  private async calculateEmployeeSalaryComponents(
    employee: any,
    salaryDetails: any[],
    payrollYear?: number,
    payrollMonth?: number,
  ) {
    let basicSalary = parseFloat(employee.basic_salary) || 0

    // If payroll year and month are provided, get basic salary from history
    if (payrollYear && payrollMonth) {
      const historicalBasicSalary = await this.getBasicSalaryFromHistory(
        employee.id,
        employee.company_id,
        payrollYear,
        payrollMonth,
      )

      // Use historical basic salary if found, otherwise fallback to employee's current basic salary
      if (historicalBasicSalary > 0) {
        basicSalary = historicalBasicSalary
      }
    }

    let totalEarnings = basicSalary
    let totalDeductions = 0

    const dynamicComponents = []

    // Process each salary setting dynamically
    for (const salaryDetail of salaryDetails) {
      const salarySetting = salaryDetail.salarySetting
      if (!salarySetting) continue

      const calculatedAmount = this.calculateDynamicAmount(
        salaryDetail.individual_value,
        salarySetting.type_value,
        basicSalary,
      )

      // Create dynamic component object
      const component = {
        id: salarySetting.id,
        name: salarySetting.name,
        type: salarySetting.type, // earning or deduction
        type_value: salarySetting.type_value, // percentage or fixed
        individual_value: salaryDetail.individual_value,
        calculated_amount: calculatedAmount,
      }

      dynamicComponents.push(component)

      // Add to totals
      if (salarySetting.type === "earning") {
        totalEarnings += calculatedAmount
      } else if (salarySetting.type === "deduction") {
        totalDeductions += calculatedAmount
      }
    }

    return {
      dynamic_components: dynamicComponents,
      total_earnings: totalEarnings,
      total_deductions: totalDeductions,
      net_payable: totalEarnings - totalDeductions,
    }
  }

  /**
   * Calculate dynamic component amount (reused from previous implementation)
   */
  private calculateDynamicAmount(
    individualValue: string,
    typeValue: string,
    baseAmount: number,
  ): number {
    const value = parseFloat(individualValue)

    if (typeValue === "percentage") {
      return (baseAmount * value) / 100
    } else {
      return value
    }
  }

  /**
   * Get currency symbol from company profile
   */
  private async getCurrencySymbol(companyId: number): Promise<string> {
    try {
      const companyProfile: any =
        await this.companyProfileRepository.getByParams({
          where: { company_id: companyId },
          findOne: true,
        })

      return companyProfile?.currency || "₹"
    } catch (error) {
      return "₹" // Default fallback
    }
  }

  async finalizePayroll(payrollCycleId: number, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const payrollCycle: any = await this.payrollCycleRepository.getByParams({
      where: { id: payrollCycleId, company_id: decoded.company_id },
      findOne: true,
    })

    if (isEmpty(payrollCycle)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll cycle" }),
      )
    }

    if (payrollCycle.status !== PayrollCycleStatus.DRAFT) {
      return failureResponse(
        code.VALIDATION,
        validationMessage("Payroll cycle is already finalized or paid"),
      )
    }

    // Update status to finalized
    payrollCycle.status = PayrollCycleStatus.FINALIZED
    payrollCycle.locked_by = decoded.user_id
    payrollCycle.locked_at = new Date()
    payrollCycle.updated_by = decoded.user_id

    await this.payrollCycleRepository.save(payrollCycle)

    return successResponse(
      code.SUCCESS,
      successMessage("Payroll cycle finalized successfully"),
    )
  }

  /**
   * Calculate employee attendance for a specific month
   */
  private async calculateEmployeeAttendance(
    employeeId: number,
    companyId: number,
    month: number,
    year: number,
    defaultWorkingDays: number,
  ) {
    const monthStart = moment({ year, month: month - 1, day: 1 })
    const monthEnd = moment(monthStart).endOf("month")

    // Fetch approved leaves overlapping this payroll month
    const leaveRequests: any = await this.leaveRequestRepository.getByParams({
      where: {
        employee_id: employeeId,
        company_id: companyId,
        status: LeaveRequestStatus.APPROVED,
        from_date: { lte: monthEnd.toDate() },
        to_date: { gte: monthStart.toDate() },
      },
      relations: ["leaveType:id,is_paid"],
    })

    let paidLeaveDays = 0
    let unpaidLeaveDays = 0

    if (leaveRequests.length > 0) {
      for (const leave of leaveRequests) {
        const leaveStart = moment(leave.from_date, "YYYY-MM-DD")
        const leaveEnd = moment(leave.to_date, "YYYY-MM-DD")

        // Clamp boundaries within the payroll month
        const effectiveStart = moment.max(leaveStart, monthStart)
        const effectiveEnd = moment.min(leaveEnd, monthEnd)

        // Calculate leave days based on leave_day_type
        let leaveDays: number
        if (
          leave.leave_day_type === LeaveDayType.FIRST_HALF ||
          leave.leave_day_type === LeaveDayType.SECOND_HALF
        ) {
          // Half-day leaves count as 0.5 days
          leaveDays = 0.5
        } else {
          // Full day leaves - calculate normally
          leaveDays = effectiveEnd.diff(effectiveStart, "days") + 1
        }

        if (leave.leaveType?.is_paid === 1) {
          paidLeaveDays += leaveDays
        } else {
          unpaidLeaveDays += leaveDays
        }
      }
    }

    const totalLeaveDays = paidLeaveDays + unpaidLeaveDays

    const presentDays = Math.max(0, defaultWorkingDays - totalLeaveDays)

    return {
      working_days: defaultWorkingDays,
      present_days: presentDays,
      paid_leaves: paidLeaveDays,
      unpaid_leaves: unpaidLeaveDays,
    }
  }

  async updatePayrollAttendance(
    payrollEntryId: number,
    attendanceData: {
      employee_id?: number
      payroll_cycle_id?: number
      working_days?: number
      present_days?: number
      paid_leaves?: number
      unpaid_leaves?: number
    },
    token: string,
  ) {
    try {
      const decoded = verifyJwtToken(token)

      if (!decoded) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.invalid_token),
        )
      }

      let payrollEntry: any = null

      // If employee_id and payroll_cycle_id are provided, try to find or create entry
      if (attendanceData.employee_id && attendanceData.payroll_cycle_id) {
        // First try to find existing entry by employee_id and payroll_cycle_id
        payrollEntry = await this.payrollEntryRepository.getByParams({
          where: {
            employee_id: Number(attendanceData.employee_id),
            payroll_cycle_id: Number(attendanceData.payroll_cycle_id),
            company_id: decoded.company_id,
          },
          relations: [
            "payroll_cycle:id,status,month,year",
            "employee:id,basic_salary,net_payable_salary,gross_salary",
          ],
          findOne: true,
        })

        // If no entry exists, create a new one
        if (!payrollEntry) {
          // Get employee details
          const employee: any = await this.employeeRepository.getByParams({
            where: {
              id: Number(attendanceData.employee_id),
              company_id: decoded.company_id,
            },
            findOne: true,
          })

          if (!employee) {
            return failureResponse(
              code.VALIDATION,
              errorMessage(messageKey.data_not_found, { ":data": "Employee" }),
            )
          }

          // Get payroll cycle details
          const payrollCycle: any =
            await this.payrollCycleRepository.getByParams({
              where: {
                id: Number(attendanceData.payroll_cycle_id),
                company_id: decoded.company_id,
              },
              findOne: true,
            })

          if (!payrollCycle) {
            return failureResponse(
              code.VALIDATION,
              errorMessage(messageKey.data_not_found, {
                ":data": "Payroll cycle",
              }),
            )
          }

          // Check if payroll cycle is in draft status
          if (payrollCycle.status !== PayrollCycleStatus.DRAFT) {
            return failureResponse(
              code.VALIDATION,
              validationMessage(
                "Payroll entry cannot be created as the cycle is finalized or paid",
              ),
            )
          }

          // Get company settings for working days
          const companySettings: any =
            await this.companySettingsRepository.getByParams({
              where: { company_id: decoded.company_id },
              findOne: true,
            })

          const defaultWorkingDays =
            companySettings?.working_days_per_month || 22

          // Calculate attendance for this employee and month
          const attendanceCalculated = await this.calculateEmployeeAttendance(
            Number(attendanceData.employee_id),
            decoded.company_id,
            payrollCycle.month,
            payrollCycle.year,
            defaultWorkingDays,
          )

          // Create new payroll entry with calculated attendance
          const newPayrollEntry = new PayrollEntry()
          Object.assign(newPayrollEntry, {
            company_id: decoded.company_id,
            payroll_cycle_id: payrollCycle.id,
            employee_id: Number(attendanceData.employee_id),
            working_days: attendanceCalculated.working_days,
            present_days: attendanceCalculated.present_days,
            paid_leaves: attendanceCalculated.paid_leaves,
            unpaid_leaves: attendanceCalculated.unpaid_leaves,
            total_earnings: 0, // Will be calculated below
            total_deductions: 0,
            net_payable: 0, // Will be calculated below
            created_by: decoded.user_id,
            updated_by: decoded.user_id,
          })

          payrollEntry = await this.payrollEntryRepository.save(newPayrollEntry)

          // Reload with relations
          payrollEntry = await this.payrollEntryRepository.getByParams({
            where: { id: payrollEntry.id },
            relations: [
              "payroll_cycle:id,status,month,year",
              "employee:id,basic_salary,net_payable_salary,gross_salary",
            ],
            findOne: true,
          })
        }
      } else {
        // Original logic: find by payroll entry ID
        payrollEntry = await this.payrollEntryRepository.getByParams({
          where: { id: payrollEntryId, company_id: decoded.company_id },
          relations: [
            "payroll_cycle:id,status",
            "employee:id,basic_salary,net_payable_salary,gross_salary",
          ],
          findOne: true,
        })
      }

      if (isEmpty(payrollEntry)) {
        return failureResponse(
          code.VALIDATION,
          errorMessage(messageKey.data_not_found, { ":data": "Payroll entry" }),
        )
      }

      // Check if payroll cycle is in draft status
      if (payrollEntry.payroll_cycle.status !== PayrollCycleStatus.DRAFT) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(
            "Payroll entry cannot be edited as the cycle is finalized or paid",
          ),
        )
      }

      // Update attendance fields
      if (attendanceData.working_days !== undefined) {
        payrollEntry.working_days = attendanceData.working_days
      }
      if (attendanceData.present_days !== undefined) {
        payrollEntry.present_days = attendanceData.present_days
      }
      if (attendanceData.paid_leaves !== undefined) {
        payrollEntry.paid_leaves = attendanceData.paid_leaves
      }
      if (attendanceData.unpaid_leaves !== undefined) {
        payrollEntry.unpaid_leaves = attendanceData.unpaid_leaves
      }

      // Get employee salary details for proper calculation
      const employeeSalaryDetails: any =
        await this.employeeSalaryDetailRepository.getByParams({
          where: { employee_id: payrollEntry.employee_id },
          relations: ["salarySetting:id,name,type,type_value,value"],
        })

      // Calculate salary components
      const salaryComponents = await this.calculateEmployeeSalaryComponents(
        payrollEntry.employee,
        employeeSalaryDetails,
        payrollEntry.payroll_cycle.year,
        payrollEntry.payroll_cycle.month,
      )

      // Add TDS as a deduction to salary components
      this.addTdsToSalaryComponents(payrollEntry.employee, salaryComponents)

      // Calculate pro-rated amounts based on attendance

      const proRatedTotalEarnings = Number(salaryComponents.total_earnings)

      const proRatedTotalDeductions = Number(salaryComponents.total_deductions)

      // Calculate unpaid leave deduction
      const grossSalary =
        payrollEntry.employee.gross_salary ||
        payrollEntry.employee.net_payable_salary ||
        salaryComponents.total_earnings
      const unpaidLeaveDeduction =
        payrollEntry.working_days > 0 && payrollEntry.unpaid_leaves > 0
          ? (parseFloat(grossSalary) / payrollEntry.working_days) *
            payrollEntry.unpaid_leaves
          : 0

      // Add unpaid leave deduction to total deductions
      const finalTotalDeductions =
        proRatedTotalDeductions + unpaidLeaveDeduction

      // Update payroll entry with calculated amounts and attendance data
      const updatedPayrollData = {
        working_days: payrollEntry.working_days,
        present_days: payrollEntry.present_days,
        paid_leaves: payrollEntry.paid_leaves,
        unpaid_leaves: payrollEntry.unpaid_leaves,
        total_earnings: proRatedTotalEarnings,
        total_deductions: finalTotalDeductions,
        net_payable: proRatedTotalEarnings - finalTotalDeductions,
        updated_by: decoded.user_id,
      }

      // Apply updates to the payroll entry
      Object.assign(payrollEntry, updatedPayrollData)
      await this.payrollEntryRepository.save(payrollEntry)

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_update, { ":data": "Payroll entry" }),
      )
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Get salary slip data
   */
  async getSalarySlip(payrollEntryId: number, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const payrollEntry: any = await this.payrollEntryRepository.getByParams({
      where: {
        id: payrollEntryId,
        company_id: decoded.company_id,
      },
      relations: [
        "employee:id,first_name,last_name,email,department_id,joining_date,basic_salary,gross_salary,tds_amount,net_payable_salary.department:id,name",
        "payroll_cycle:id,month,year",
        "company:id,name,address_id.address:id,address,lat,long",
      ],
      findOne: true,
    })

    if (isEmpty(payrollEntry)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Payroll entry" }),
      )
    }

    // Get employee salary details for salary components
    const employeeSalaryDetails: any =
      await this.employeeSalaryDetailRepository.getByParams({
        where: { employee_id: payrollEntry.employee_id },
        relations: ["salarySetting:id,name,type,type_value,value"],
      })

    // Calculate salary components
    const salaryComponents = await this.calculateEmployeeSalaryComponents(
      payrollEntry.employee,
      employeeSalaryDetails,
      payrollEntry.payroll_cycle.year,
      payrollEntry.payroll_cycle.month,
    )

    // // Add TDS as a deduction to salary components
    // this.addTdsToSalaryComponents(payrollEntry.employee, salaryComponents)

    const proRatedBasicSalary = Number(payrollEntry.employee.basic_salary)

    // Create earnings and deductions arrays
    const earnings = [
      {
        component_label: "Basic Salary",
        amount: proRatedBasicSalary,
      },
    ]

    const deductions = []

    // Calculate the actual amounts that were used in the payroll calculation
    // This should match the same logic used in updatePayrollAttendance

    // Calculate unpaid leave deduction (same as in updatePayrollAttendance)
    const grossSalary =
      payrollEntry.employee.gross_salary ||
      payrollEntry.employee.net_payable_salary ||
      salaryComponents.total_earnings
    const unpaidLeaveDeduction =
      payrollEntry.working_days > 0 && payrollEntry.unpaid_leaves > 0
        ? (parseFloat(grossSalary) / payrollEntry.working_days) *
          payrollEntry.unpaid_leaves
        : 0
    const tds = payrollEntry?.employee?.tds_amount || 0

    // Add unpaid leave deduction if applicable
    if (unpaidLeaveDeduction > 0) {
      deductions.push({
        component_label: "Unpaid Leave Deduction",
        amount: unpaidLeaveDeduction,
      })
    }

    deductions.push({
      component_label: "Tax Deducted at Source (TDS)",
      amount: tds,
    })

    // Add dynamic components to earnings and deductions
    if (
      salaryComponents.dynamic_components &&
      salaryComponents.dynamic_components.length > 0
    ) {
      for (const component of salaryComponents.dynamic_components) {
        if (component.type === "earning") {
          earnings.push({
            component_label: component.name,
            amount: component.calculated_amount,
          })
        } else if (component.type === "deduction") {
          deductions.push({
            component_label: component.name,
            amount: component.calculated_amount,
            code: component.code || null, // Include code for identification
            is_system_component: component.is_system_component || false,
          })
        }
      }
    }

    // Format month name
    const monthNames = [
      "January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December",
    ]
    const monthName = monthNames[payrollEntry.payroll_cycle.month - 1]

    const salarySlipData = {
      company: {
        name: payrollEntry.company?.name || "Company Name",
        address: payrollEntry.company?.address?.address || null,
        lat: payrollEntry.company?.address?.lat || null,
        long: payrollEntry.company?.address?.long || null,
      },

      employee: {
        name: `${payrollEntry.employee.first_name} ${payrollEntry.employee.last_name}`,
        employee_id:
          payrollEntry.employee.employee_id ||
          `EMP-${payrollEntry.employee.id}`,
        department: payrollEntry.employee.department?.name,
        designation: payrollEntry.employee.designation || "Employee",
        basic: parseFloat(payrollEntry.employee.basic_salary) || 0,
        joining_date: payrollEntry.employee.joining_date,
      },
      payroll_period: {
        month: monthName,
        year: payrollEntry.payroll_cycle.year,
        pay_period: `${monthName} ${payrollEntry.payroll_cycle.year}`,
      },
      attendance: {
        working_days: payrollEntry.working_days,
        present_days: payrollEntry.present_days,
        paid_leaves: payrollEntry.paid_leaves,
        unpaid_leaves: payrollEntry.unpaid_leaves,
        days_paid: payrollEntry.working_days - payrollEntry.unpaid_leaves,
        lop_days: payrollEntry.unpaid_leaves,
      },
      earnings,
      deductions,
      totals: {
        total_earnings: payrollEntry.total_earnings,
        total_deductions: payrollEntry.total_deductions,
        net_payable: payrollEntry.net_payable,
      },
      payment_date: payrollEntry.created_at?.toISOString().split("T")[0], // Current date
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Salary slip" }),
      salarySlipData as any,
    )
  }

  /**
   * Get employee salary slips list
   */
  async getEmployeeSalarySlips(query: any = {}, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10, search, month, year } = query

    const skip = (page - 1) * limit
    const take = parseInt(limit.toString())

    // Build where conditions for payroll entries
    const whereConditions: any = {
      company_id: decoded.company_id,
      employee_id: decoded.employee_id, // Employee can only see their own slips
    }

    // Build where conditions for payroll cycle
    const cycleWhereConditions: any = {}

    if (month !== undefined && month !== null) {
      cycleWhereConditions.month = month
    }

    if (year !== undefined && year !== null) {
      cycleWhereConditions.year = year
    }

    // Handle search functionality
    const searchConditions: any = {}
    if (search) {
      const searchLower = search.toString().toLowerCase()

      // If it's a valid year (number)
      if (!isNaN(Number(searchLower))) {
        searchConditions.year = Number(searchLower)
      }

      // If user searched month name (full or partial)
      const matchedMonth = Object.keys(monthMap).find((m) =>
        m.startsWith(searchLower),
      )

      if (matchedMonth) {
        searchConditions.month = monthMap[matchedMonth]
      }
    }

    // Get payroll entries with cycle information
    const payrollEntries: any = await this.payrollEntryRepository.getByParams({
      where: whereConditions,
      relations: [
        "payroll_cycle:id,month,year,status,locked_at",
        "employee:id,first_name,last_name,email",
      ],
      orderBy: { created_at: "DESC" },
      take,
      skip,
    })

    // Filter by cycle conditions and search
    let filteredEntries = []
    if (payrollEntries.data && payrollEntries.data.length > 0) {
      filteredEntries = payrollEntries.data.filter((entry: any) => {
        const cycle = entry.payroll_cycle

        // Check cycle where conditions
        if (
          cycleWhereConditions.month &&
          cycle.month !== cycleWhereConditions.month
        ) {
          return false
        }
        if (
          cycleWhereConditions.year &&
          cycle.year !== cycleWhereConditions.year
        ) {
          return false
        }

        // Check search conditions
        if (searchConditions.month && cycle.month !== searchConditions.month) {
          return false
        }
        if (searchConditions.year && cycle.year !== searchConditions.year) {
          return false
        }

        // Only show finalized payroll cycles
        return cycle.status !== PayrollCycleStatus.DRAFT
      })
    }

    // Format the response data
    const formattedSlips = filteredEntries.map((entry: any) => {
      const cycle = entry.payroll_cycle

      // Format month name
      const monthNames = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
      ]
      const monthName = monthNames[cycle.month - 1]

      // Format processed date (use locked_at or current date)
      const processedDate = cycle.locked_at
        ? new Date(cycle.locked_at).toLocaleDateString("en-GB")
        : new Date().toLocaleDateString("en-GB")

      return {
        id: entry.id,
        payroll_entry_id: entry.id,
        month: monthName,
        year: cycle.year,
        processed_date: processedDate,
        net_salary: parseFloat(entry.net_payable) || 0,
        total_earnings: parseFloat(entry.total_earnings) || 0,
        total_deductions: parseFloat(entry.total_deductions) || 0,
        payroll_cycle: {
          id: cycle.id,
          month: cycle.month,
          year: cycle.year,
          status: cycle.status,
        },
      }
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Salary slips" }),
      {
        data: formattedSlips,
        total: formattedSlips.length,
        page: Math.floor(skip / take) + 1,
        limit: take,
      } as any,
    )
  }

  /**
   * Export Salary Slip as PDF
   */
  async exportSalarySlipPDF(payrollEntryId: number, token: string, res: any) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    try {
      // Get salary slip data
      const salarySlipResponse: any = await this.getSalarySlip(
        payrollEntryId,
        token,
      )

      if (salarySlipResponse.code !== code.SUCCESS) {
        return salarySlipResponse
      }

      const salarySlipData = salarySlipResponse.data

      // Generate HTML content for PDF
      const htmlContent = await this.generateSalarySlipHTML(
        salarySlipData,
        decoded.company_id,
      )

      // Launch puppeteer and generate PDF
      const browser = await puppeteer.launch({
        headless: true,
        args: ["--no-sandbox", "--disable-setuid-sandbox"],
      })

      const page = await browser.newPage()
      await page.setContent(htmlContent, { waitUntil: "networkidle0" })

      const pdfBuffer = await page.pdf({
        format: "A4",
        printBackground: true,
        margin: {
          top: "20px",
          right: "20px",
          bottom: "20px",
          left: "20px",
        },
      })

      await browser.close()

      // Set response headers
      res.setHeader("Content-Type", "application/pdf")
      res.setHeader(
        "Content-Disposition",
        `attachment; filename=salary-slip-${payrollEntryId}.pdf`,
      )
      res.setHeader("Content-Length", pdfBuffer.length)

      res.end(pdfBuffer)
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Export Payroll Entries to Excel
   */
  async exportPayrollEntries(payrollCycleId: number, token: string, res: any) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    try {
      // Get currency symbol
      const currencySymbol = await this.getCurrencySymbol(decoded.company_id)

      // Get payroll entries data without pagination
      const payrollDataResponse: any = await this.getProcessPayrollData(
        payrollCycleId,
        { page: 1, limit: 999999 },
        token,
      )

      if (payrollDataResponse.code !== code.SUCCESS) {
        return payrollDataResponse
      }

      const payrollData = payrollDataResponse.data.data

      // Get all unique salary components from all employees to create dynamic columns
      const allSalaryComponents = new Set()
      payrollData.forEach((entry: any) => {
        if (entry.salary_components && entry.salary_components.length > 0) {
          entry.salary_components.forEach((component: any) => {
            allSalaryComponents.add(`${component.name}|${component.type}`)
          })
        }
      })

      const workbook = new ExcelJS.Workbook()
      const sheet = workbook.addWorksheet("Payroll Entries")

      // Set up base columns
      let columns = [
        { header: "Employee ID", key: "employee_id", width: 15 },
        { header: "Employee Name", key: "employee_name", width: 25 },
        { header: "Department", key: "department", width: 20 },
        { header: "Basic Salary", key: "basic_salary", width: 15 },
        { header: "Gross Salary", key: "gross_salary", width: 15 },
        { header: "Working Days", key: "working_days", width: 15 },
        { header: "Present Days", key: "present_days", width: 15 },
        { header: "Paid Leaves", key: "paid_leaves", width: 15 },
        { header: "Unpaid Leaves", key: "unpaid_leaves", width: 15 },
      ]

      // Add dynamic salary component columns
      const sortedComponents = Array.from(allSalaryComponents).sort()
      sortedComponents.forEach((componentKey: string) => {
        const [name, type] = componentKey.split("|")
        columns.push({
          header: `${name} (${type.charAt(0).toUpperCase() + type.slice(1)})`,
          key: `component_${name.toLowerCase().replace(/\s+/g, "_")}`,
          width: 18,
        })
      })

      // Add total columns
      columns = columns.concat([
        { header: "Total Earnings", key: "total_earnings", width: 15 },
        { header: "Total Deductions", key: "total_deductions", width: 15 },
        { header: "Net Payable", key: "net_payable", width: 15 },
      ])

      sheet.columns = columns

      // Add data rows
      payrollData.forEach((entry: any) => {
        const rowData: any = {
          employee_id: entry.employee?.id || entry.employee_id || "N/A",
          employee_name:
            entry.employee_name ||
            `${entry?.first_name || ""} ${entry?.last_name || ""}`.trim() ||
            "N/A",
          department:
            entry.employee?.department?.name || entry.department?.name || "N/A",
          basic_salary: `${currencySymbol}${Math.round(entry.employee?.basic_salary || entry.basic || 0)}`,
          gross_salary: `${currencySymbol}${Math.round(entry.employee?.gross_salary || entry.gross_salary || 0)}`,
          working_days: entry.working_days || 0,
          present_days: entry.present_days || 0,
          paid_leaves: entry.paid_leaves || 0,
          unpaid_leaves: entry.unpaid_leaves || 0,
          total_earnings: `${currencySymbol}${Math.round(entry.total_earnings) || 0}`,
          total_deductions: `${currencySymbol}${Math.round(entry.total_deductions) || 0}`,
          net_payable: `${currencySymbol}${Math.round(entry.net_payable || entry.net_salary) || 0}`,
        }

        // Add dynamic salary component values
        if (entry.salary_components && entry.salary_components.length > 0) {
          entry.salary_components.forEach((component: any) => {
            const componentKey = `component_${component.name.toLowerCase().replace(/\s+/g, "_")}`
            rowData[componentKey] =
              `${currencySymbol}${Math.round(component.calculated_amount) || 0}`
          })
        }

        // Fill missing component values with 0
        sortedComponents.forEach((componentKey: string) => {
          const [name] = componentKey.split("|")
          const key = `component_${name.toLowerCase().replace(/\s+/g, "_")}`
          if (!(key in rowData)) {
            rowData[key] = 0
          }
        })

        sheet.addRow(rowData)
      })

      // Set response headers
      res.setHeader(
        "Content-Type",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      )
      res.setHeader(
        "Content-Disposition",
        "attachment; filename=payroll-entries.xlsx",
      )

      await workbook.xlsx.write(res)
      res.end()
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Generate HTML content for salary slip PDF
   */
  private async generateSalarySlipHTML(
    salarySlipData: any,
    companyId: number,
  ): Promise<string> {
    const currencySymbol = await this.getCurrencySymbol(companyId)
    const {
      company,
      employee,
      payroll_period,
      attendance,
      earnings,
      deductions,
      totals,
    } = salarySlipData

    return `
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Salary Slip</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                margin: 0;
                padding: 20px;
                font-size: 12px;
                line-height: 1.4;
            }
            .header {
                text-align: center;
                margin-bottom: 30px;
                border-bottom: 2px solid #333;
                padding-bottom: 15px;
            }
            .company-name {
                font-size: 24px;
                font-weight: bold;
                color: #333;
                margin-bottom: 5px;
            }
            .company-address {
                font-size: 14px;
                color: #666;
                margin-bottom: 5px;
            }
            .slip-title {
                font-size: 18px;
                font-weight: bold;
                color: #666;
            }
            .employee-info {
                display: flex;
                justify-content: space-between;
                margin-bottom: 20px;
                background-color: #f8f9fa;
                padding: 15px;
                border-radius: 5px;
            }
            .info-section {
                flex: 1;
            }
            .info-row {
                margin-bottom: 8px;
            }
            .label {
                font-weight: bold;
                color: #333;
                display: inline-block;
                width: 120px;
            }
            .value {
                color: #666;
            }
            .salary-details {
                display: flex;
                justify-content: space-between;
                margin-bottom: 20px;
            }
            .earnings, .deductions {
                flex: 1;
                margin: 0 10px;
            }
            .section-title {
                background-color: #007bff;
                color: white;
                padding: 8px 12px;
                margin-bottom: 10px;
                font-weight: bold;
                border-radius: 3px;
            }
            .deductions .section-title {
                background-color: #dc3545;
            }
            table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 15px;
            }
            th, td {
                border: 1px solid #ddd;
                padding: 8px;
                text-align: left;
            }
            th {
                background-color: #f8f9fa;
                font-weight: bold;
            }
            .amount {
                text-align: right;
                font-weight: bold;
            }
            .totals {
                background-color: #f8f9fa;
                padding: 15px;
                border-radius: 5px;
                margin-top: 20px;
            }
            .total-row {
                display: flex;
                justify-content: space-between;
                margin-bottom: 8px;
                font-size: 14px;
            }
            .net-payable {
                font-size: 16px;
                font-weight: bold;
                color: #28a745;
                border-top: 2px solid #28a745;
                padding-top: 8px;
            }
            .attendance-info {
                background-color: #e9ecef;
                padding: 15px;
                border-radius: 5px;
                margin-bottom: 20px;
            }
            .attendance-grid {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 15px;
            }
            @media print {
                body { margin: 0; }
                .no-print { display: none; }
            }
        </style>
    </head>
    <body>
        <div class="header">
            <div class="company-name">${company.name}</div>
             <div class="company-address">
                <span class="value">${company.address}</span>
            </div>
           
            <div style="margin-top: 10px; color: #666;">
                Pay Period: ${payroll_period.pay_period}
            </div>
           
        </div>

        <div class="employee-info">
            <div class="info-section">
                <div class="info-row">
                    <span class="label">Employee Name:</span>
                    <span class="value">${employee.name}</span>
                </div>
                <div class="info-row">
                    <span class="label">Employee ID:</span>
                    <span class="value">${employee.employee_id}</span>
                </div>
                <div class="info-row">
                    <span class="label">Department:</span>
                    <span class="value">${employee.department || "N/A"}</span>
                </div>
            </div>
            <div class="info-section">
                <div class="info-row">
                    <span class="label">Designation:</span>
                    <span class="value">${employee.designation}</span>
                </div>
                <div class="info-row">
                    <span class="label">Joining Date:</span>
                    <span class="value">${employee.joining_date || "N/A"}</span>
                </div>
                <div class="info-row">
                    <span class="label">Basic Salary:</span>
                    <span class="value">${currencySymbol}${employee.basic.toLocaleString()}</span>
                </div>
            </div>
        </div>

        <div class="attendance-info">
            <div class="section-title" style="background-color: #6c757d;">Attendance Details</div>
            <div class="attendance-grid">
                <div><strong>Working Days:</strong> ${attendance.working_days}</div>
                <div><strong>Present Days:</strong> ${attendance.present_days}</div>
                <div><strong>Paid Leaves:</strong> ${attendance.paid_leaves}</div>
                <div><strong>Unpaid Leaves:</strong> ${attendance.unpaid_leaves}</div>
            </div>
        </div>

        <div class="salary-details">
            <div class="earnings">
                <div class="section-title">Earnings</div>
                <table>
                    <thead>
                        <tr>
                            <th>Component</th>
                            <th class="amount">Amount (${currencySymbol})</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${earnings
                          .map(
                            (earning) => `
                            <tr>
                                <td>${earning.component_label}</td>
                                <td class="amount">${earning.amount.toLocaleString()}</td>
                            </tr>
                        `,
                          )
                          .join("")}
                    </tbody>
                </table>
            </div>

            <div class="deductions">
                <div class="section-title">Deductions</div>
                <table>
                    <thead>
                        <tr>
                            <th>Component</th>
                            <th class="amount">Amount (${currencySymbol})</th>
                        </tr>
                    </thead>
                    <tbody>
                        ${
                          deductions.length > 0
                            ? deductions
                                .map(
                                  (deduction) => `
                            <tr>
                                <td>${deduction.component_label}</td>
                                <td class="amount">${deduction.amount.toLocaleString()}</td>
                            </tr>
                        `,
                                )
                                .join("")
                            : '<tr><td colspan="2" style="text-align: center;">No Deductions</td></tr>'
                        }
                    </tbody>
                </table>
            </div>
        </div>

        <div class="totals">
            <div class="total-row">
                <span><strong>Total Earnings:</strong></span>
                <span><strong>${currencySymbol}${totals.total_earnings.toLocaleString()}</strong></span>
            </div>
            <div class="total-row">
                <span><strong>Total Deductions:</strong></span>
                <span><strong>${currencySymbol}${totals.total_deductions.toLocaleString()}</strong></span>
            </div>
            <div class="total-row net-payable">
                <span><strong>Net Payable:</strong></span>
                <span><strong>${currencySymbol}${totals.net_payable.toLocaleString()}</strong></span>
            </div>
        </div>

        <div style="margin-top: 30px; text-align: center; color: #666; font-size: 10px;">
            <p>This is a computer-generated salary slip and does not require a signature.</p>
            <p>Generated on: ${new Date().toLocaleDateString()}</p>
        </div>
    </body>
    </html>
    `
  }

  /**
   * Add TDS as a deduction to salary components
   */
  private addTdsToSalaryComponents(employee: any, salaryComponents: any): void {
    // Get monthly TDS amount from employee record
    const monthlyTdsAmount = Math.round(employee.tds_amount || 0)

    // Only add TDS if there's an amount to deduct
    if (monthlyTdsAmount > 0) {
      // Add TDS to dynamic components array
      const tdsComponent = {
        id: "TDS", // Fixed ID for TDS
        name: "Tax Deducted at Source (TDS)",
        type: "deduction",
        type_value: "fixed",
        individual_value: monthlyTdsAmount.toString(),
        calculated_amount: monthlyTdsAmount,
        code: "TDS", // Standard code for TDS
        is_system_component: true, // Mark as system component
      }

      // Add to dynamic components array
      if (!salaryComponents.dynamic_components) {
        salaryComponents.dynamic_components = []
      }
      salaryComponents.dynamic_components.push(tdsComponent)

      // Update total deductions
      salaryComponents.total_deductions =
        (salaryComponents.total_deductions || 0) + monthlyTdsAmount

      // Update net payable
      salaryComponents.net_payable =
        salaryComponents.total_earnings - salaryComponents.total_deductions
    }
  }
}
