<?php
// ============================================================
// cron/process_monthly_leave_accrual.php
// ============================================================
// For every leave type a company has set to "Monthly" accrual
// (Companies → Leave Policy):
//   1. Looks at what's left of LAST month's monthly share.
//   2. Depending on that leave type's settings for this company:
//        - Carry Forward ON  → left as-is in the balance (capped at
//          Max Carry Forward — anything above that is lapsed/encashed
//          per the rules below, same as any other unused leave).
//        - Encashable ON     → paid out (logged in leave_encashment_log)
//          and removed from the balance.
//        - Neither           → simply lapses ("use it or lose it" —
//          exactly "that month's leave must be used within that
//          month").
//   3. Credits the NEW month's share into the employee's balance.
//
// Safe to re-run — each employee/leave-type/month combination is only
// ever processed once (see leave_accrual_log's unique key).
//
// Run from the command line / cron only.
// ============================================================

if (php_sapi_name() !== 'cli') {
    http_response_code(403);
    die("This script can only be run from the command line (cron), not a browser.\n");
}

require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../includes/functions.php';

$db = Database::getInstance();

$lastMonth = (int)date('n', strtotime('first day of last month'));
$lastYear  = (int)date('Y', strtotime('first day of last month'));
$thisMonth = (int)date('n');
$thisYear  = (int)date('Y');

echo "[" . date('Y-m-d H:i:s') . "] Processing monthly leave accrual — closing {$lastMonth}/{$lastYear}, opening {$thisMonth}/{$thisYear}\n";

try {
    $db->fetchOne("SELECT 1 FROM company_leave_policy LIMIT 1");
} catch (Throwable $e) {
    die("company_leave_policy table not found — run migration_company_leave_policy.sql first.\n");
}

$policies = $db->fetchAll(
    "SELECT clp.*, lt.name AS leave_name, lt.days_per_year
       FROM company_leave_policy clp
       JOIN leave_types lt ON lt.id = clp.leave_type_id
      WHERE clp.accrual_period = 'monthly' AND clp.is_active = 1"
);

$totalEmployeesTouched = 0;

foreach ($policies as $pol) {
    $companyId    = (int)$pol['company_id'];
    $leaveTypeId  = (int)$pol['leave_type_id'];
    $monthlyShare = round(((float)$pol['days_per_year']) / 12, 2);
    if ($monthlyShare <= 0) continue;

    $employees = $db->fetchAll("SELECT id FROM employees WHERE company_id=? AND status='active'", [$companyId]);
    echo "  - {$pol['leave_name']} (company #{$companyId}): monthly share {$monthlyShare} days, " . count($employees) . " employee(s)\n";

    foreach ($employees as $emp) {
        $empId = (int)$emp['id'];

        // Already processed this employee/leave-type/month? Skip (idempotent).
        $already = $db->fetchOne("SELECT id FROM leave_accrual_log WHERE employee_id=? AND leave_type_id=? AND month=? AND year=?", [$empId, $leaveTypeId, $lastMonth, $lastYear]);
        if ($already) continue;

        $bal = $db->fetchOne("SELECT id,allocated,used FROM employee_leave_balance WHERE employee_id=? AND leave_type_id=? AND year=?", [$empId, $leaveTypeId, $lastYear]);
        $allocated = $bal ? (float)$bal['allocated'] : 0.0;
        $used      = $bal ? (float)$bal['used'] : 0.0;
        $remaining = $allocated - $used;

        // How much of last month's share is still sitting unused —
        // capped at one month's worth (a simple, defensible proxy since
        // balances are tracked as a running total, not a per-month ledger).
        $unused = max(0.0, min($monthlyShare, $remaining));

        $outcome = 'fully_used';
        $encashAmount = 0.0;

        if ($unused > 0) {
            if ($pol['carry_forward']) {
                // Stays in the balance — but cap the TOTAL carried balance
                // at max_carry_forward, if set. Anything above that is
                // treated as lapsed (or encashed, if also encashable).
                $maxCarry = (float)$pol['max_carry_forward'];
                if ($maxCarry > 0 && $remaining > $maxCarry) {
                    $excess = $remaining - $maxCarry;
                    $allocated -= $excess;
                    $outcome = $pol['encashable'] ? 'encashed' : 'lapsed';
                    if ($pol['encashable']) {
                        $encashAmount = calcLeaveEncashment($db, $empId, $excess, (float)$pol['encashment_rate_percent']);
                        $db->execute("INSERT INTO leave_encashment_log (employee_id,leave_type_id,days,amount,processed_by) VALUES (?,?,?,?,NULL)", [$empId,$leaveTypeId,$excess,$encashAmount]);
                    }
                } else {
                    $outcome = 'carried_forward';
                }
            } elseif ($pol['encashable']) {
                $allocated -= $unused;
                $encashAmount = calcLeaveEncashment($db, $empId, $unused, (float)$pol['encashment_rate_percent']);
                $db->execute("INSERT INTO leave_encashment_log (employee_id,leave_type_id,days,amount,processed_by) VALUES (?,?,?,?,NULL)", [$empId,$leaveTypeId,$unused,$encashAmount]);
                $outcome = 'encashed';
            } else {
                // Lapses — "use it or lose it".
                $allocated -= $unused;
                $outcome = 'lapsed';
            }
        }

        // Credit the NEW month's share.
        $allocated += $monthlyShare;

        if ($bal) {
            $db->execute("UPDATE employee_leave_balance SET allocated=? WHERE id=?", [$allocated, $bal['id']]);
        } else {
            $db->execute("INSERT INTO employee_leave_balance (employee_id,leave_type_id,year,allocated,used) VALUES (?,?,?,?,0)", [$empId,$leaveTypeId,$lastYear,$allocated]);
        }

        $db->execute(
            "INSERT INTO leave_accrual_log (employee_id,leave_type_id,month,year,credited_days,unused_days,outcome,encashment_amount) VALUES (?,?,?,?,?,?,?,?)",
            [$empId, $leaveTypeId, $lastMonth, $lastYear, $monthlyShare, $unused, $outcome, $encashAmount]
        );

        if ($outcome === 'encashed' && $encashAmount > 0) {
            notify($db, $empId, 'Leave Encashed', round($unused,2) . " unused {$pol['leave_name']} day(s) were encashed for ₹" . number_format($encashAmount,2) . ".", 'leave');
        } elseif ($outcome === 'lapsed' && $unused > 0) {
            notify($db, $empId, 'Leave Lapsed', round($unused,2) . " unused {$pol['leave_name']} day(s) from last month lapsed (not used within the month).", 'leave');
        }

        $totalEmployeesTouched++;
    }
}

echo "[" . date('Y-m-d H:i:s') . "] Done. Employee-leave-type combinations processed: {$totalEmployeesTouched}\n";

// Per-day salary estimate for encashment payouts: gross_monthly / 26
// working days, adjusted by the leave type's encashment rate %. Returns
// 0 (logged, not silently guessed) if the employee has no salary record yet.
function calcLeaveEncashment(Database $db, int $employeeId, float $days, float $ratePercent): float {
    $gross = $db->fetchValue("SELECT gross_monthly FROM employee_salary WHERE employee_id=?", [$employeeId]);
    if (!$gross) return 0.0;
    $perDay = ((float)$gross) / 26;
    return round($perDay * $days * ($ratePercent / 100), 2);
}

// ============================================================
// CPANEL CRON JOB SETUP (do this once):
//
// 1. cPanel → Cron Jobs
// 2. Schedule: 1st of every month, e.g. 3 AM (run AFTER the late
//    penalty cron, so late-deduction leave usage for the closing
//    month is already reflected):
//      Minute: 0   Hour: 3   Day: 1   Month: *   Weekday: *
// 3. Command (adjust the PHP path/domain to match your account):
//      /usr/local/bin/php /home/adrieltech/public_html/seyalix/cron/process_monthly_leave_accrual.php >> /home/adrieltech/public_html/seyalix/cron/leave_accrual.log 2>&1
// 4. Save. Check leave_accrual.log any time via File Manager to see
//    what happened each month.
// ============================================================
