<?php
class PayrollEngine
{
    private PDO $pdo;

    const OT_WEEKDAY = 1.5;
    const OT_WEEKEND = 2.0;
    const OT_HOLIDAY = 3.0;

    const PIT_BRACKETS = [
        [5_000_000,   0.05],
        [10_000_000,  0.10],
        [18_000_000,  0.15],
        [32_000_000,  0.20],
        [52_000_000,  0.25],
        [80_000_000,  0.30],
        [PHP_INT_MAX, 0.35],
    ];

    const SI_EMPLOYEE_RATE    = 0.105;
    const SI_COMPANY_RATE     = 0.215;
    const PERSONAL_DEDUCTION  = 15_500_000;
    const DEPENDANT_DEDUCTION = 6_200_000;
    const LATE_GRACE_MINUTES  = 5;
    const LATE_BLOCK_MINUTES  = 30;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    // ── Tính ngày công chuẩn ────────────────────────────────────────────
    public function calcWorkingDays(string $from, string $to): int
    {
        $stmt = $this->pdo->prepare(
            "SELECT holiday_date FROM holidays WHERE holiday_date BETWEEN ? AND ?"
        );
        $stmt->execute([$from, $to]);
        $holidays = $stmt->fetchAll(PDO::FETCH_COLUMN);

        $count   = 0;
        $current = new DateTime($from);
        $end     = new DateTime($to);
        while ($current <= $end) {
            if ($current->format('N') != 7 && !in_array($current->format('Y-m-d'), $holidays))
                $count++;
            $current->modify('+1 day');
        }
        return $count;
    }

    // ── Tính lương 1 nhân viên ──────────────────────────────────────────
    public function calculate(int $periodId, int $userId): array
    {
        $period  = $this->getPeriod($periodId);
        $profile = $this->getProfile($userId);
        $salary  = $this->getSalaryComponents($userId);

        $basicSalary    = (int)($salary['basic']            ?? 0);
        $mealAllow      = (int)($salary['meal']             ?? 0);
        $clothesAllow   = (int)($salary['clothes']          ?? 0);
        $phoneAllow     = (int)($salary['phone']            ?? 0);
        $transportAllow = (int)($salary['transport']        ?? 0);
        $performBonus   = (int)($salary['performance']      ?? 0);
        $attendBonus    = (int)($salary['attendance_bonus'] ?? 0);

        $workingDays = (int)$period['working_days'];

        // ── Lương 1 ngày / 1 giờ ─────────────────────────────────────
        $salaryPerDay  = $workingDays > 0 ? round($basicSalary / $workingDays) : 0;
        $salaryPerHour = round($salaryPerDay / 8);

        // ── Dữ liệu chấm công / nghỉ phép / OT ────────────────────���──
        $att = $this->getAttendanceData($userId, $period['period_from'], $period['period_to']);
        $lv  = $this->getLeaveData($userId, $period['period_from'], $period['period_to']);
        $ot  = $this->getOTData($userId, $period['period_from'], $period['period_to']);

        $actualWorkdays     = (float)($att['actual_workdays']      ?? 0);
        $paidLeaveDays      = (float)($lv['paid_leave_days']        ?? 0);
        $otherPaidLeaveDays = (float)($lv['other_paid_leave_days']  ?? 0);
        $unpaidLeaveDays    = (float)($lv['unpaid_leave_days']      ?? 0);

        $lateMinutes     = (float)($att['total_late_minutes']  ?? 0)
                         + (float)($att['total_early_minutes'] ?? 0);
        $otWeekdayHours  = (float)($ot['ot_weekday_hours'] ?? 0);
        $otWeekendHours  = (float)($ot['ot_weekend_hours'] ?? 0);
        $otHolidayHours  = (float)($ot['ot_holiday_hours'] ?? 0);
        $isLateWarning   = (bool)($att['has_early_leave']    ?? false);
        $lateWarningNote = (string)($att['early_leave_dates'] ?? '');

        // ── Tổng ngày hưởng lương ─────────────────────────────────────
        $totalPaidDays = $actualWorkdays + $paidLeaveDays + $otherPaidLeaveDays;

        // ── Đi muộn/về sớm ────────────────────────────────────────────
        [$lateHours, $lateDeduction] = $this->calcLateDeduction($lateMinutes, $salaryPerHour);

        // ── Lương cơ bản thực nhận ────────────────────────────────────
        $basicReceived = $workingDays > 0
            ? round($basicSalary * ($totalPaidDays / $workingDays)) : 0;

        // ── Trợ cấp thực nhận ─────────────────────────────────────────
        $hasUnpaid      = $unpaidLeaveDays > 0;
        $allowanceRatio = $workingDays > 0
            ? min(1.0, $totalPaidDays / $workingDays) : 0;

        $mealReceived      = $hasUnpaid ? round($mealAllow      * $allowanceRatio) : $mealAllow;
        $clothesReceived   = $hasUnpaid ? round($clothesAllow   * $allowanceRatio) : $clothesAllow;
        $phoneReceived     = $hasUnpaid ? round($phoneAllow     * $allowanceRatio) : $phoneAllow;
        $transportReceived = $hasUnpaid ? round($transportAllow * $allowanceRatio) : $transportAllow;

        // ── Chuyên cần ────────────────────────────────────────────────
        $attendEligible = ($unpaidLeaveDays == 0);
        $attendReceived = ($attendEligible && $attendBonus > 0) ? $attendBonus : 0;

        // ── OT ────────────────────────────────────────────────────────
        $otWeekdayAmt = round($otWeekdayHours * $salaryPerHour * self::OT_WEEKDAY);
        $otWeekendAmt = round($otWeekendHours * $salaryPerHour * self::OT_WEEKEND);
        $otHolidayAmt = round($otHolidayHours * $salaryPerHour * self::OT_HOLIDAY);
        $totalOtAmt   = $otWeekdayAmt + $otWeekendAmt + $otHolidayAmt;

        // ── Phép tồn ──────────────────────────────────────────────────
        // ✅ Tính theo tháng làm việc thực tế thay vì cứng 14
        $leaveTotal     = $this->calcAnnualLeave($userId, (int)$period['period_year']);
        $leaveUsed      = $this->getLeaveUsed($userId, (int)$period['period_year']);
        $leaveRemaining = max(0, $leaveTotal - $leaveUsed);
        $leavePayout    = 0;

        // ── BHXH ──────────────────────────────────────────────────────
        $hasInsurance = (int)($profile['has_insurance'] ?? 0);
        $siEmployee   = $hasInsurance ? round($basicSalary * self::SI_EMPLOYEE_RATE) : 0;
        $siCompany    = $hasInsurance ? round($basicSalary * self::SI_COMPANY_RATE)  : 0;

        // ── Thuế TNCN ─────────────────────────────────────────────────
        $dependants         = (int)($profile['dependants'] ?? 0);
        $dependantDeduction = $dependants * self::DEPENDANT_DEDUCTION;

        $grossForTax = $basicReceived + $mealReceived + $clothesReceived
                     + $phoneReceived + $transportReceived + $performBonus
                     + $attendReceived + $totalOtAmt;

        $otExcludePit = 0;

        $taxableIncome = max(0,
            $grossForTax - $siEmployee
            - self::PERSONAL_DEDUCTION
            - $dependantDeduction
        );
        $pitAmount = $this->calcPIT($taxableIncome);

        // ── Gross & Net ───────────────────────────────────────────────
        $grossSalary = $grossForTax + $leavePayout;
        $netSalary   = $grossSalary - $siEmployee - $pitAmount - $lateDeduction;

        return [
            'period_id'                  => $periodId,
            'user_id'                    => $userId,
            'basic_salary'               => $basicSalary,
            'working_days_standard'      => $workingDays,
            'salary_per_day'             => $salaryPerDay,
            'salary_per_hour'            => $salaryPerHour,
            'basic_salary_per_hour'      => $salaryPerHour,
            'actual_workdays'            => $actualWorkdays,
            'paid_leave_days'            => $paidLeaveDays,
            'other_paid_leave_days'      => $otherPaidLeaveDays,
            'unpaid_leave_days'          => $unpaidLeaveDays,
            'late_early_hours'           => $lateHours,
            'late_early_deduction'       => $lateDeduction,
            'total_paid_days'            => $totalPaidDays,
            'basic_salary_received'      => $basicReceived,
            'meal_allowance'             => $mealAllow,
            'meal_received'              => $mealReceived,
            'clothes_allowance'          => $clothesAllow,
            'clothes_received'           => $clothesReceived,
            'phone_allowance'            => $phoneAllow,
            'phone_received'             => $phoneReceived,
            'transport_allowance'        => $transportAllow,
            'transport_received'         => $transportReceived,
            'performance_bonus'          => $performBonus,
            'attendance_bonus'           => $attendReceived,
            'attendance_bonus_eligible'  => $attendEligible ? 1 : 0,
            'ot_weekday_hours'           => $otWeekdayHours,
            'ot_weekend_hours'           => $otWeekendHours,
            'ot_holiday_hours'           => $otHolidayHours,
            'ot_weekday_amount'          => $otWeekdayAmt,
            'ot_weekend_amount'          => $otWeekendAmt,
            'ot_holiday_amount'          => $otHolidayAmt,
            'total_ot_amount'            => $totalOtAmt,
            'annual_leave_total'         => $leaveTotal,
            'annual_leave_used'          => $leaveUsed,
            'annual_leave_remaining'     => $leaveRemaining,
            'annual_leave_payout'        => $leavePayout,
            'other_income'               => 0,
            'adjustment'                 => 0,
            'other_bonus'                => 0,
            'has_social_insurance'       => $hasInsurance,
            'si_employee'                => $siEmployee,
            'si_company'                 => $siCompany,
            'dependants'                 => $dependants,
            'personal_deduction'         => self::PERSONAL_DEDUCTION,
            'dependant_deduction'        => $dependantDeduction,
            'ot_exclude_pit'             => $otExcludePit,
            'taxable_income'             => $taxableIncome,
            'pit_amount'                 => $pitAmount,
            'late_deduction'             => $lateDeduction,
            'gross_salary'               => $grossSalary,
            'advance_payment'            => 0,
            'net_salary'                 => $netSalary,
            'pit_adjustment'             => 0,
            'bank_transfer'              => $netSalary,
            'remark'                     => '',
            'is_late_warning'            => $isLateWarning ? 1 : 0,
            'late_warning_note'          => $lateWarningNote,
            'manually_adjusted'          => 0,
        ];
    }

    // ── Tính số ngày phép theo tháng làm việc thực tế ───────────────────
    // Luật VN: tính từ ngày vào → cuối năm, mỗi tháng = leavePerYear/12
    // Nếu vào sau ngày 15 → không tính tháng đó
    // VD: vào 10/3 → tính từ T3, còn 10 tháng → 10 × (12/12) = 10 ngày
    // VD: vào 20/3 → tính từ T4, còn 9 tháng  → 9  × (12/12) = 9 ngày
    private function calcAnnualLeave(int $userId, int $periodYear): int
    {
        $stmt = $this->pdo->prepare("
            SELECT date_joined, annual_leave_total
            FROM employee_profiles
            WHERE user_id = ?
        ");
        $stmt->execute([$userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        $leavePerYear = (int)($row['annual_leave_total'] ?? 12);
        $dateJoined   = $row['date_joined'] ?? null;

        // Chưa có ngày vào → trả full
        if (!$dateJoined) return $leavePerYear;

        $joined   = new DateTime($dateJoined);
        $joinYear = (int)$joined->format('Y');

        // Vào trước năm tính → full phép
        if ($joinYear < $periodYear) return $leavePerYear;

        // Vào sau năm tính → chưa đủ điều kiện
        if ($joinYear > $periodYear) return 0;

        // Vào đúng năm tính → tính theo tháng
        $joinMonth = (int)$joined->format('m');
        $joinDay   = (int)$joined->format('d');

        // Nếu vào sau ngày 1 → không tính tháng đó, bắt đầu từ tháng sau
        $startMonth = ($joinDay > 1) ? $joinMonth + 1 : $joinMonth;

        // S��� tháng được tính trong năm
        $monthsWorked = max(0, 12 - $startMonth + 1);

        return (int)round($monthsWorked * $leavePerYear / 12);
    }

    // ── Chấm công từ attendance_logs ────────────────────────────────────
    private function getAttendanceData(int $userId, string $from, string $to): array
    {
        try {
            $stmt = $this->pdo->prepare("
                SELECT
                    COUNT(CASE WHEN check_in IS NOT NULL THEN 1 END)
                        AS actual_workdays,
                    COALESCE(SUM(CASE WHEN is_late = 1 THEN late_minutes ELSE 0 END), 0)
                        AS total_late_minutes,
                    COALESCE(SUM(CASE WHEN early_leave = 1 THEN early_leave_minutes ELSE 0 END), 0)
                        AS total_early_minutes,
                    MAX(CASE WHEN early_leave = 1 THEN 1 ELSE 0 END)
                        AS has_early_leave,
                    GROUP_CONCAT(
                        CASE WHEN early_leave = 1
                        THEN DATE_FORMAT(work_date, '%d/%m/%Y') END
                        ORDER BY work_date
                        SEPARATOR ', '
                    ) AS early_leave_dates
                FROM attendance_logs
                WHERE user_id  = ?
                  AND work_date BETWEEN ? AND ?
            ");
            $stmt->execute([$userId, $from, $to]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: $this->emptyAttendance();
        } catch (\Throwable $e) {
            error_log("getAttendanceData error uid=$userId: " . $e->getMessage());
            return $this->emptyAttendance();
        }
    }

    // ── Nghỉ phép từ leave_requests ─────────────────────────────────────
    private function getLeaveData(int $userId, string $from, string $to): array
    {
        try {
            $stmt = $this->pdo->prepare("
                SELECT
                    COALESCE(SUM(CASE WHEN leave_type = 'annual'
                        THEN total_days ELSE 0 END), 0)         AS paid_leave_days,
                    COALESCE(SUM(CASE WHEN leave_type IN ('other','sick')
                        THEN total_days ELSE 0 END), 0)         AS other_paid_leave_days,
                    COALESCE(SUM(CASE WHEN leave_type = 'unpaid'
                        THEN total_days ELSE 0 END), 0)         AS unpaid_leave_days
                FROM leave_requests
                WHERE user_id    = ?
                  AND status     = 'approved'
                  AND start_date BETWEEN ? AND ?
            ");
            $stmt->execute([$userId, $from, $to]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: $this->emptyLeave();
        } catch (\Throwable $e) {
            error_log("getLeaveData error uid=$userId: " . $e->getMessage());
            return $this->emptyLeave();
        }
    }

    // ── OT từ overtime_requests ──────────────────────────────────────────
    private function getOTData(int $userId, string $from, string $to): array
    {
        try {
            $stmt = $this->pdo->prepare("
                SELECT
                    COALESCE(SUM(CASE WHEN ot_type = 'weekday'
                        THEN hours ELSE 0 END), 0) AS ot_weekday_hours,
                    COALESCE(SUM(CASE WHEN ot_type = 'weekend'
                        THEN hours ELSE 0 END), 0) AS ot_weekend_hours,
                    COALESCE(SUM(CASE WHEN ot_type = 'holiday'
                        THEN hours ELSE 0 END), 0) AS ot_holiday_hours
                FROM overtime_requests
                WHERE user_id = ?
                  AND status  = 'approved'
                  AND ot_date BETWEEN ? AND ?
            ");
            $stmt->execute([$userId, $from, $to]);
            return $stmt->fetch(PDO::FETCH_ASSOC) ?: $this->emptyOT();
        } catch (\Throwable $e) {
            error_log("getOTData error uid=$userId: " . $e->getMessage());
            return $this->emptyOT();
        }
    }

    // ── Số ngày phép annual đã dùng trong năm ───────────────────────────
    private function getLeaveUsed(int $userId, int $year): float
    {
        try {
            $stmt = $this->pdo->prepare("
                SELECT COALESCE(SUM(total_days), 0)
                FROM leave_requests
                WHERE user_id    = ?
                  AND status     = 'approved'
                  AND leave_type = 'annual'
                  AND YEAR(start_date) = ?
            ");
            $stmt->execute([$userId, $year]);
            return (float)$stmt->fetchColumn();
        } catch (\Throwable $e) {
            error_log("getLeaveUsed error uid=$userId: " . $e->getMessage());
            return 0;
        }
    }

    // ── Block đi muộn/về sớm ────────────────────────────────────────────
    private function calcLateDeduction(float $totalMinutes, int $salaryPerHour): array
    {
        if ($totalMinutes <= self::LATE_GRACE_MINUTES) return [0.0, 0];
        $over      = $totalMinutes - self::LATE_GRACE_MINUTES;
        $blocks    = (int)ceil($over / self::LATE_BLOCK_MINUTES);
        $lateHours = $blocks * (self::LATE_BLOCK_MINUTES / 60);
        $deduction = (int)round($blocks * ($salaryPerHour / 2));
        return [$lateHours, $deduction];
    }

    // ── Thuế TNCN 7 bậc lũy tiến ────────────────────────────────────────
    private function calcPIT(int $taxableIncome): int
    {
        if ($taxableIncome <= 0) return 0;
        $tax = 0; $prev = 0;
        foreach (self::PIT_BRACKETS as [$limit, $rate]) {
            if ($taxableIncome <= $prev) break;
            $tax += (min($taxableIncome, $limit) - $prev) * $rate;
            $prev = $limit;
            if ($taxableIncome <= $limit) break;
        }
        return (int)round($tax);
    }

    // ── Helpers: empty arrays ────────────────────────────────────────────
    private function emptyAttendance(): array
    {
        return [
            'actual_workdays'     => 0,
            'total_late_minutes'  => 0,
            'total_early_minutes' => 0,
            'has_early_leave'     => 0,
            'early_leave_dates'   => '',
        ];
    }

    private function emptyLeave(): array
    {
        return [
            'paid_leave_days'       => 0,
            'other_paid_leave_days' => 0,
            'unpaid_leave_days'     => 0,
        ];
    }

    private function emptyOT(): array
    {
        return [
            'ot_weekday_hours' => 0,
            'ot_weekend_hours' => 0,
            'ot_holiday_hours' => 0,
        ];
    }

    // ── DB helpers ──────────────────────────────────────────────────────
    private function getPeriod(int $id): array
    {
        $stmt = $this->pdo->prepare("SELECT * FROM payroll_periods WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }

    private function getProfile(int $userId): array
    {
        $stmt = $this->pdo->prepare("SELECT * FROM employee_profiles WHERE user_id = ?");
        $stmt->execute([$userId]);
        $profile = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$profile) return [];
        if (!isset($profile['annual_leave_total'])) $profile['annual_leave_total'] = 12;
        if (!isset($profile['dependants']))          $profile['dependants']         = 0;
        if (!isset($profile['has_insurance']))       $profile['has_insurance']      = 0;
        return $profile;
    }

    private function getSalaryComponents(int $userId): array
    {
        $stmt = $this->pdo->prepare("
            SELECT
                MAX(CASE WHEN sc.component_code = 'basic'            THEN es.amount END) AS basic,
                MAX(CASE WHEN sc.component_code = 'meal'             THEN es.amount END) AS meal,
                MAX(CASE WHEN sc.component_code = 'clothes'          THEN es.amount END) AS clothes,
                MAX(CASE WHEN sc.component_code = 'phone'            THEN es.amount END) AS phone,
                MAX(CASE WHEN sc.component_code = 'transport'        THEN es.amount END) AS transport,
                MAX(CASE WHEN sc.component_code = 'performance'      THEN es.amount END) AS performance,
                MAX(CASE WHEN sc.component_code = 'attendance_bonus' THEN es.amount END) AS attendance_bonus
            FROM employee_salaries es
            LEFT JOIN salary_components sc ON es.component_id = sc.id
            WHERE es.user_id = ? AND es.is_active = 1
        ");
        $stmt->execute([$userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }
}