<?php

namespace App\Modules\HR\Leave\Services;

use App\Modules\Admin\Tenancy\Models\Tenant;
use App\Modules\HR\Leave\DTOs\LeaveRequestSubmitData;
use App\Modules\HR\Leave\Events\LeaveRequestSubmitted;
use App\Modules\HR\Leave\Models\LeaveRequest;
use App\Modules\HR\Leave\Repositories\LeaveRequestRepository;
use App\Modules\HR\Leave\Repositories\LeaveTypeRepository;
use App\Modules\HR\Leave\Support\LeaveRequestStatus;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class LeaveRequestService
{
    public function __construct(
        private readonly LeaveRequestRepository $requests,
        private readonly LeaveTypeRepository $types,
        private readonly LeaveBalanceService $balances,
        private readonly Dispatcher $events,
    ) {}

    public function submit(Tenant $tenant, LeaveRequestSubmitData $data, int $userId): LeaveRequest
    {
        return DB::transaction(function () use ($tenant, $data, $userId): LeaveRequest {
            $p = $data->toPersistenceArray();
            $type = $this->types->findForTenant((int) $tenant->id, (int) $p['leave_type_id']);
            if ($type === null || ! $type->is_active) {
                throw ValidationException::withMessages([
                    'leave_type_id' => __('leave.errors.invalid_type'),
                ]);
            }

            $days = $this->resolveDays($type->allow_half_day, (bool) $p['is_half_day'], $p['start_date'], $p['end_date']);
            if ((bool) $p['is_half_day'] && ! $type->allow_half_day) {
                throw ValidationException::withMessages([
                    'is_half_day' => __('leave.errors.half_day_not_allowed'),
                ]);
            }

            $this->balances->assertCanRequest($type, (int) $p['employee_id'], $days, $p['start_date']);

            $row = $this->requests->create([
                'tenant_id' => $tenant->id,
                'employee_id' => $p['employee_id'],
                'requested_by' => $userId,
                'leave_type_id' => $p['leave_type_id'],
                'start_date' => CarbonImmutable::parse($p['start_date'])->toDateString(),
                'end_date' => CarbonImmutable::parse($p['end_date'])->toDateString(),
                'total_days' => $days,
                'is_half_day' => (bool) $p['is_half_day'],
                'reason' => $p['reason'] ?? null,
                'status' => LeaveRequestStatus::PENDING,
            ]);
            $fresh = $row->fresh(['employee', 'leaveType']);
            DB::afterCommit(function () use ($fresh, $userId): void {
                $this->events->dispatch(new LeaveRequestSubmitted($fresh, $userId));
            });

            return $fresh;
        });
    }

    private function resolveDays(bool $allowHalf, bool $isHalf, string $start, string $end): float
    {
        if ($isHalf && $allowHalf) {
            return 0.5;
        }

        $s = CarbonImmutable::parse($start);
        $e = CarbonImmutable::parse($end);

        return (float) ($s->diffInDays($e) + 1);
    }
}
