<?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 (Art. 47-54, 91):
 *   Annual: 21 days (<10y), 30 days (>=10y or age>=50), 45 days (hazardous)
 *   Casual: 7 days/year, max 2 consecutive
 *   Sick: 75% first 3 months, 85% next 3, then unpaid
 *   Maternity: 90 days paid, max 3 career, termination protection
 *   Hajj: 30 days unpaid, once in career
 *   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;

        $daysUnder10 = '21.0';
        $daysOver10 = '30.0';

        $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, '.', '');
        }

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

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

        if ($yearsOfService >= 10 || $age >= 50) {
            return $daysOver10;
        }

        return $daysUnder10;
    }

    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)
        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' => 'تم استنفاد الحد الأقصى المسموح لهذا النوع من الإجازات خلال مسيرتك المهنية'];
            }
        }

        $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;
    }
}
