<?php

namespace App\Modules\HR\Leave\Services;

use App\Modules\HR\Leave\Models\LeaveBalance;
use App\Modules\HR\Leave\Models\LeaveRequest;
use App\Modules\HR\Leave\Models\LeaveType;
use App\Modules\HR\Leave\Repositories\LeaveBalanceRepository;
use App\Modules\HR\Leave\Repositories\LeaveTypeRepository;
use Illuminate\Validation\ValidationException;

class LeaveBalanceService
{
    public function __construct(
        private readonly LeaveBalanceRepository $balances,
        private readonly LeaveTypeRepository $types,
    ) {}

    public function assertCanRequest(LeaveType $type, int $employeeId, float $days, string $startDate): void
    {
        if ($type->annual_limit === null) {
            return;
        }

        $year = (int) date('Y', strtotime($startDate));
        $balance = $this->ensureBalance(
            (int) $type->tenant_id,
            $employeeId,
            (int) $type->id,
            $year,
            (float) $type->annual_limit,
        );
        $pending = $this->balances->sumPendingDays(
            (int) $type->tenant_id,
            $employeeId,
            (int) $type->id,
            $year,
        );
        $available = $balance->remainingDays() - $pending;

        if ($available + 0.001 >= $days) {
            return;
        }

        if ($type->allow_negative_balance) {
            return;
        }

        throw ValidationException::withMessages([
            'leave_type_id' => __('leave.errors.insufficient_balance', [
                'available' => number_format(max(0, $available), 2),
                'requested' => number_format($days, 2),
            ]),
        ]);
    }

    public function consume(LeaveRequest $request): void
    {
        $type = $request->leaveType ?? $this->types->findForTenant(
            (int) $request->tenant_id,
            (int) $request->leave_type_id,
        );
        if ($type === null || $type->annual_limit === null) {
            return;
        }

        $year = (int) date('Y', strtotime((string) $request->start_date));
        $this->ensureBalance(
            (int) $request->tenant_id,
            (int) $request->employee_id,
            (int) $request->leave_type_id,
            $year,
            (float) $type->annual_limit,
        );
        $locked = $this->balances->lockForUpdate(
            (int) $request->tenant_id,
            (int) $request->employee_id,
            (int) $request->leave_type_id,
            $year,
        );
        if ($locked !== null) {
            $this->balances->addUsedDays($locked, (float) $request->total_days);
        }
    }

    public function restore(LeaveRequest $request): void
    {
        $type = $request->leaveType;
        if ($type === null || $type->annual_limit === null) {
            return;
        }

        $year = (int) date('Y', strtotime((string) $request->start_date));
        $locked = $this->balances->lockForUpdate(
            (int) $request->tenant_id,
            (int) $request->employee_id,
            (int) $request->leave_type_id,
            $year,
        );
        if ($locked !== null) {
            $this->balances->addUsedDays($locked, -1 * (float) $request->total_days);
        }
    }

    private function ensureBalance(
        int $tenantId,
        int $employeeId,
        int $leaveTypeId,
        int $year,
        float $entitled,
    ): LeaveBalance {
        return $this->balances->findOrCreate($tenantId, $employeeId, $leaveTypeId, $year, $entitled);
    }
}
