<?php
declare(strict_types=1);

namespace App\Modules\HR\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\HR\Models\HrLeaveBalance;
use App\Modules\HR\Models\HrLeaveType;
use App\Modules\HR\Models\HrLeaveRequest;
use App\Modules\HR\Models\HrEmployeeProfile;
use App\Modules\Workflow\Services\WorkflowEngine;

/**
 * Leave Entitlement & Management Service
 *
 * Egyptian Labour Law 2025 (New Law):
 *   Annual: 15 days (1st year), 21 days (after 1y), 30 days (>=10y or age>=50), 45 days (disability)
 *   Casual: 7 days/year, max 2 consecutive
 *   Sick: 75% first 3 months, 85% next 3, then unpaid
 *   Maternity: 4 months (120 days) paid at 100% comprehensive salary, max 3 career
 *   Hajj: 30 days PAID, once in career, requires 5 years service
 *   Childcare: unpaid, max 3 career, 1y service, 24-month gap between uses (female)
 *   Paternity: 1 day paid, max 3 career (male)
 *   Exam: paid, actual days, requires 10-day advance schedule, not deducted from annual
 *   Carry-over: max 3 years accumulation, min 6 consecutive/year
 */
final class LeaveService
{
    public static function calculateAnnualEntitlement(array $profile): string
    {
        $db = App::getInstance()->db();
        $hireDate = $profile['hire_date'] ?? null;

        // Load configurable tier values
        $daysFirstYear = '15.0';
        $daysUnder10 = '21.0';
        $daysOver10 = '30.0';
        $daysDisability = '45.0';

        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_first_year']);
        if ($row && $row['config_value'] !== '') {
            $daysFirstYear = number_format((float) $row['config_value'], 1, '.', '');
        }
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_days_under_10y']);
        if ($row && $row['config_value'] !== '') {
            $daysUnder10 = number_format((float) $row['config_value'], 1, '.', '');
        }
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_days_over_10y']);
        if ($row && $row['config_value'] !== '') {
            $daysOver10 = number_format((float) $row['config_value'], 1, '.', '');
        }
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_disability']);
        if ($row && $row['config_value'] !== '') {
            $daysDisability = number_format((float) $row['config_value'], 1, '.', '');
        }

        // Disability override — Art. 47 (2025 Law): 45 days regardless of tenure
        if (!empty($profile['has_disability']) && (int) $profile['has_disability'] === 1) {
            return $daysDisability;
        }

        if (!$hireDate) {
            return $daysFirstYear;
        }

        $yearsOfService = (float) HrEmployeeProfile::getYearsOfService($hireDate);
        $age = self::calculateAge($profile['date_of_birth'] ?? '');

        // Tier 3: 10+ years of service OR age 50+
        if ($yearsOfService >= 10 || $age >= 50) {
            return $daysOver10;
        }

        // Tier 2: after completing 1 year
        if ($yearsOfService >= 1) {
            return $daysUnder10;
        }

        // Tier 1: first year of employment
        return $daysFirstYear;
    }

    public static function submitRequest(array $data): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $profileId = (int) ($data['employee_profile_id'] ?? 0);
        $leaveTypeId = (int) ($data['leave_type_id'] ?? 0);
        $startDate = $data['start_date'] ?? '';
        $endDate = $data['end_date'] ?? '';

        if ($profileId <= 0 || $leaveTypeId <= 0 || $startDate === '' || $endDate === '') {
            return ['success' => false, 'error' => 'جميع الحقول المطلوبة يجب تعبئتها'];
        }

        $profile = HrEmployeeProfile::find($profileId);
        if (!$profile) {
            return ['success' => false, 'error' => 'الموظف غير موجود'];
        }

        $leaveType = $db->selectOne("SELECT * FROM hr_leave_types WHERE id = ? AND is_active = 1", [$leaveTypeId]);
        if (!$leaveType) {
            return ['success' => false, 'error' => 'نوع الإجازة غير صالح'];
        }

        // Gender restriction
        if ($leaveType['gender_restriction'] && $leaveType['gender_restriction'] !== ($profile->gender ?? '')) {
            return ['success' => false, 'error' => 'هذا النوع من الإجازات غير متاح لجنس الموظف'];
        }

        // Calculate total days
        $totalDays = self::calculateBusinessDays($startDate, $endDate, $profileId);
        $isHalfDay = (int) ($data['half_day'] ?? 0);
        if ($isHalfDay) {
            $totalDays = '0.5';
        }

        $isUnpaid = ($leaveType['category'] === 'unpaid' || ($leaveType['is_paid'] ?? 1) == 0);

        // Check system_config for unpaid max (0 = unlimited)
        $unpaidMaxDays = 0;
        if ($isUnpaid) {
            $configRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.unpaid_max_days']);
            $unpaidMaxDays = (int) ($configRow['config_value'] ?? 0);
        }

        // Validate against limits (skip for unlimited unpaid)
        if (!$isUnpaid || $unpaidMaxDays > 0) {
            $effectiveMax = $isUnpaid ? (string) $unpaidMaxDays : $leaveType['max_days_per_request'];
            if ($effectiveMax && bccomp($totalDays, $effectiveMax, 1) > 0) {
                return ['success' => false, 'error' => 'تجاوز الحد الأقصى لأيام الطلب الواحد (' . $effectiveMax . ' يوم)'];
            }
        }
        if ($leaveType['min_days_per_request'] && bccomp($totalDays, $leaveType['min_days_per_request'], 1) < 0) {
            return ['success' => false, 'error' => 'الحد الأدنى للطلب هو ' . $leaveType['min_days_per_request'] . ' يوم'];
        }

        // Check balance (skip for unpaid leave — unlimited by default)
        $year = (int) date('Y', strtotime($startDate));
        $balance = HrLeaveBalance::getBalance($profileId, $leaveTypeId, $year);
        if ($balance && !$isUnpaid) {
            $remaining = bcsub(
                bcadd(bcadd($balance['entitled_days'], $balance['carried_over_days'], 1), $balance['adjustment_days'], 1),
                bcadd($balance['used_days'], $balance['pending_days'], 1),
                1
            );
            if (bccomp($totalDays, $remaining, 1) > 0) {
                return ['success' => false, 'error' => 'الرصيد المتاح غير كافٍ (المتبقي: ' . $remaining . ' يوم)'];
            }
        }

        // Career limits (maternity, hajj, childcare, paternity)
        if ($leaveType['career_max_times']) {
            $careerCount = $db->selectOne(
                "SELECT COUNT(*) as cnt FROM hr_leave_requests
                 WHERE employee_profile_id = ? AND leave_type_id = ? AND status = 'approved' AND is_archived = 0",
                [$profileId, $leaveTypeId]
            );
            if ((int) ($careerCount['cnt'] ?? 0) >= (int) $leaveType['career_max_times']) {
                return ['success' => false, 'error' => 'تم استنفاد الحد الأقصى المسموح لهذا النوع من الإجازات خلال مسيرتك المهنية'];
            }
        }

        // Minimum service months (hajj = 60 months, childcare = 12 months)
        if (!empty($leaveType['min_service_months']) && (int) $leaveType['min_service_months'] > 0) {
            $serviceMonths = (int) (HrEmployeeProfile::getYearsOfService($profile->hire_date ?? '') * 12);
            if ($serviceMonths < (int) $leaveType['min_service_months']) {
                $requiredYears = number_format((int) $leaveType['min_service_months'] / 12, 1);
                return ['success' => false, 'error' => 'يشترط مدة خدمة لا تقل عن ' . $requiredYears . ' سنة للحصول على هذه الإجازة'];
            }
        }

        // Childcare: enforce 24-month gap between uses
        $rules = !empty($leaveType['rules_json']) ? json_decode($leaveType['rules_json'], true) : [];
        if (!empty($rules['min_gap_months_between_uses'])) {
            $lastApproved = $db->selectOne(
                "SELECT end_date FROM hr_leave_requests
                 WHERE employee_profile_id = ? AND leave_type_id = ? AND status = 'approved' AND is_archived = 0
                 ORDER BY end_date DESC LIMIT 1",
                [$profileId, $leaveTypeId]
            );
            if ($lastApproved) {
                $lastEnd = new \DateTime($lastApproved['end_date']);
                $requestStart = new \DateTime($startDate);
                $monthsDiff = (int) $lastEnd->diff($requestStart)->m + ((int) $lastEnd->diff($requestStart)->y * 12);
                if ($monthsDiff < (int) $rules['min_gap_months_between_uses']) {
                    return ['success' => false, 'error' => 'يجب مرور ' . $rules['min_gap_months_between_uses'] . ' شهر على الأقل بين كل استخدام لهذا النوع من الإجازات'];
                }
            }
        }

        // Casual leave: max consecutive days validation
        if ($leaveType['code'] === 'casual' && !empty($leaveType['max_consecutive_days'])) {
            if (bccomp($totalDays, (string) $leaveType['max_consecutive_days'], 1) > 0) {
                return ['success' => false, 'error' => 'الحد الأقصى للإجازة العارضة المتصلة ' . $leaveType['max_consecutive_days'] . ' يوم'];
            }
        }

        $db->beginTransaction();
        try {
            $requestId = $db->insert('hr_leave_requests', [
                'employee_profile_id' => $profileId,
                'leave_type_id'       => $leaveTypeId,
                'start_date'          => $startDate,
                'end_date'            => $endDate,
                'total_days'          => $totalDays,
                'half_day'            => $isHalfDay,
                'half_day_type'       => $data['half_day_type'] ?? null,
                'reason'              => $data['reason'] ?? null,
                'document_path'       => $data['document_path'] ?? null,
                'status'              => 'pending',
                'created_at'          => date('Y-m-d H:i:s'),
                'updated_at'          => date('Y-m-d H:i:s'),
                'created_by'          => $employee ? (int) $employee->id : null,
            ]);

            // Add pending days to balance
            if ($balance) {
                HrLeaveBalance::addPendingDays((int) $balance['id'], $totalDays);
            }

            $db->commit();

            if (WorkflowEngine::hasDefinition('hr_leave_approval')) {
                WorkflowEngine::createInstance('hr_leave_approval', 'hr_leave_requests', $requestId);
            }

            EventBus::dispatch('hr.leave.submitted', [
                'request_id'  => $requestId,
                'employee_id' => $profile->employee_id,
                'leave_type'  => $leaveType['name_ar'],
                'days'        => $totalDays,
            ]);

            return ['success' => true, 'request_id' => $requestId];

        } catch (\Throwable $e) {
            $db->rollBack();
            Logger::error("Leave request failed: " . $e->getMessage(), ['profile_id' => $profileId]);
            return ['success' => false, 'error' => 'فشل تقديم طلب الإجازة: ' . $e->getMessage()];
        }
    }

    public static function approveRequest(int $requestId): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $request = $db->selectOne("SELECT * FROM hr_leave_requests WHERE id = ? AND is_archived = 0", [$requestId]);
        if (!$request || $request['status'] !== 'pending') {
            return ['success' => false, 'error' => 'الطلب غير موجود أو غير قابل للاعتماد'];
        }

        $db->beginTransaction();
        try {
            $db->update('hr_leave_requests', [
                'status'      => 'approved',
                'approved_by' => $employee ? (int) $employee->id : null,
                'approved_at' => date('Y-m-d H:i:s'),
                'updated_at'  => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$requestId]);

            // Move from pending to used
            $year = (int) date('Y', strtotime($request['start_date']));
            $balance = HrLeaveBalance::getBalance(
                (int) $request['employee_profile_id'],
                (int) $request['leave_type_id'],
                $year
            );
            if ($balance) {
                HrLeaveBalance::deductDays((int) $balance['id'], $request['total_days']);
            }

            $db->commit();

            try {
                WorkflowEngine::transitionByEntity('hr_leave_requests', $requestId, 'approve');
            } catch (\Throwable $e) {
                Logger::warning("Workflow transition failed for leave approve", ['request_id' => $requestId, 'error' => $e->getMessage()]);
            }

            $profile = HrEmployeeProfile::find((int) $request['employee_profile_id']);
            EventBus::dispatch('hr.leave.approved', [
                'request_id'   => $requestId,
                'employee_id'  => $profile ? $profile->employee_id : null,
                'employee_name'=> $profile ? trim(($profile->first_name_ar ?? '') . ' ' . ($profile->last_name_ar ?? '')) : '',
                'leave_type'   => $request['leave_type_id'],
                'leave_start'  => $request['start_date'],
                'leave_end'    => $request['end_date'],
                'days'         => $request['total_days'],
            ]);

            return ['success' => true];

        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => 'فشل اعتماد الإجازة: ' . $e->getMessage()];
        }
    }

    public static function rejectRequest(int $requestId, string $reason = ''): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $request = $db->selectOne("SELECT * FROM hr_leave_requests WHERE id = ? AND is_archived = 0", [$requestId]);
        if (!$request || $request['status'] !== 'pending') {
            return ['success' => false, 'error' => 'الطلب غير موجود أو غير قابل للرفض'];
        }

        $db->beginTransaction();
        try {
            $db->update('hr_leave_requests', [
                'status'           => 'rejected',
                'rejection_reason' => $reason,
                'approved_by'      => $employee ? (int) $employee->id : null,
                'approved_at'      => date('Y-m-d H:i:s'),
                'updated_at'       => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$requestId]);

            // Release pending days
            $year = (int) date('Y', strtotime($request['start_date']));
            $balance = HrLeaveBalance::getBalance(
                (int) $request['employee_profile_id'],
                (int) $request['leave_type_id'],
                $year
            );
            if ($balance) {
                HrLeaveBalance::releasePendingDays((int) $balance['id'], $request['total_days']);
            }

            $db->commit();

            try {
                WorkflowEngine::transitionByEntity('hr_leave_requests', $requestId, 'reject');
            } catch (\Throwable $e) {
                Logger::warning("Workflow transition failed for leave reject", ['request_id' => $requestId, 'error' => $e->getMessage()]);
            }

            $profile = HrEmployeeProfile::find((int) $request['employee_profile_id']);
            EventBus::dispatch('hr.leave.rejected', [
                'request_id'    => $requestId,
                'employee_id'   => $profile ? $profile->employee_id : null,
                'employee_name' => $profile ? trim(($profile->first_name_ar ?? '') . ' ' . ($profile->last_name_ar ?? '')) : '',
            ]);

            return ['success' => true];

        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => 'فشل رفض الإجازة: ' . $e->getMessage()];
        }
    }

    public static function initializeYearBalances(int $profileId, int $year): void
    {
        $db = App::getInstance()->db();
        $profile = HrEmployeeProfile::find($profileId);
        if (!$profile) return;

        $leaveTypes = HrLeaveType::allActive();
        $annualEntitlement = self::calculateAnnualEntitlement($profile->toArray());

        foreach ($leaveTypes as $lt) {
            $existing = HrLeaveBalance::getBalance($profileId, (int) $lt['id'], $year);
            if ($existing) continue;

            $entitled = $lt['default_days_per_year'];
            if ($lt['code'] === 'annual') {
                $entitled = $annualEntitlement;
            } elseif ($lt['code'] === 'maternity') {
                $entitled = self::getMaternityDays();
            }

            // Calculate carry-over from previous year
            $carryOver = '0.0';
            if ($lt['carry_over_allowed']) {
                $prevBalance = HrLeaveBalance::getBalance($profileId, (int) $lt['id'], $year - 1);
                if ($prevBalance) {
                    $remaining = bcsub(
                        bcadd(bcadd($prevBalance['entitled_days'], $prevBalance['carried_over_days'], 1), $prevBalance['adjustment_days'], 1),
                        $prevBalance['used_days'],
                        1
                    );
                    if (bccomp($remaining, '0', 1) > 0) {
                        $maxCarry = $lt['max_carry_over_days'] ?? '90.0';
                        $carryOver = bccomp($remaining, $maxCarry, 1) > 0 ? $maxCarry : $remaining;
                    }
                }
            }

            $db->insert('hr_leave_balances', [
                'employee_profile_id' => $profileId,
                'leave_type_id'       => (int) $lt['id'],
                'year'                => $year,
                'entitled_days'       => $entitled,
                'carried_over_days'   => $carryOver,
                'adjustment_days'     => '0.0',
                'used_days'           => '0.0',
                'pending_days'        => '0.0',
                'created_at'          => date('Y-m-d H:i:s'),
                'updated_at'          => date('Y-m-d H:i:s'),
            ]);
        }
    }

    public static function calculateBusinessDays(string $startDate, string $endDate, int $profileId): string
    {
        $start = new \DateTime($startDate);
        $end = new \DateTime($endDate);
        $days = 0;

        $profile = HrEmployeeProfile::find($profileId);
        $religion = $profile ? $profile->religion : null;

        $current = clone $start;
        while ($current <= $end) {
            $dateStr = $current->format('Y-m-d');
            if (!AttendanceService::isRestDay($profileId, $dateStr) && !HrHoliday::isHoliday($dateStr, $religion)) {
                $days++;
            }
            $current->modify('+1 day');
        }

        return number_format($days, 1, '.', '');
    }

    public static function getMaternityDays(): string
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.maternity_days']);
        return ($row && $row['config_value'] !== '') ? $row['config_value'] : '90';
    }

    public static function getMaternityMaxCareer(): int
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.maternity_max_career']);
        return ($row && $row['config_value'] !== '') ? (int) $row['config_value'] : 3;
    }

    public static function getBreastfeedingDailyHours(): string
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.breastfeeding_daily_hours']);
        return ($row && $row['config_value'] !== '') ? $row['config_value'] : '1';
    }

    public static function isEligibleForBreastfeeding(int $profileId): bool
    {
        $db = App::getInstance()->db();
        $profile = HrEmployeeProfile::find($profileId);
        if (!$profile || ($profile->gender ?? '') !== 'female') {
            return false;
        }

        $lastMaternity = $db->selectOne(
            "SELECT lr.end_date FROM hr_leave_requests lr
             JOIN hr_leave_types lt ON lt.id = lr.leave_type_id
             WHERE lr.employee_profile_id = ? AND lt.code = 'maternity' AND lr.status = 'approved' AND lr.is_archived = 0
             ORDER BY lr.end_date DESC LIMIT 1",
            [$profileId]
        );

        if (!$lastMaternity) {
            return false;
        }

        $endDate = new \DateTime($lastMaternity['end_date']);
        $now = new \DateTime();
        $monthsSinceReturn = (int) $endDate->diff($now)->m + ((int) $endDate->diff($now)->y * 12);

        // Egyptian law: breastfeeding hour for 24 months after birth
        return $monthsSinceReturn <= 24;
    }

    public static function getBreastfeedingToleranceMinutes(int $profileId): int
    {
        if (!self::isEligibleForBreastfeeding($profileId)) {
            return 0;
        }
        $hours = (float) self::getBreastfeedingDailyHours();
        return (int) ($hours * 60);
    }

    private static function calculateAge(string $dob): int
    {
        if (empty($dob)) return 0;
        $birth = new \DateTime($dob);
        $now = new \DateTime();
        return (int) $birth->diff($now)->y;
    }
}
