<?php

namespace App\Modules\HR\Overtime\Services;

use App\Modules\Admin\Tenancy\Models\Tenant;
use App\Modules\HR\Overtime\DTOs\OvertimeRequestSubmitData;
use App\Modules\HR\Overtime\Events\OvertimeRequestCancelled;
use App\Modules\HR\Overtime\Events\OvertimeRequestSubmitted;
use App\Modules\HR\Overtime\Models\OvertimeRequest;
use App\Modules\HR\Overtime\Repositories\OvertimeRequestRepository;
use App\Modules\HR\Overtime\Support\OvertimeRequestStatus;
use App\Modules\HR\Employees\Repositories\EmployeeRepository;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class OvertimeRequestService
{
    public function __construct(
        private readonly OvertimeRequestRepository $requests,
        private readonly Dispatcher $events,
        private readonly EmployeeRepository $employees,
        private readonly OvertimeRateResolver $rates,
    ) {}

    public function submit(Tenant $tenant, OvertimeRequestSubmitData $data, int $userId): OvertimeRequest
    {
        return DB::transaction(function () use ($tenant, $data, $userId): OvertimeRequest {
            $date = CarbonImmutable::parse($data->attendanceDate)->toDateString();
            $this->assertNoBlockingDuplicate((int) $tenant->id, $data->employeeId, $date);
            $employee = $this->employees->findForTenant((int) $tenant->id, $data->employeeId);
            abort_if($employee === null, 404);
            $rate = $this->rates->resolve($employee, $date);

            $row = $this->requests->create([
                'tenant_id' => $tenant->id,
                'employee_id' => $data->employeeId,
                'requested_by' => $userId,
                'attendance_date' => $date,
                'overtime_minutes' => $data->overtimeMinutes,
                'rate_category' => $rate['category'],
                'applied_multiplier' => $rate['multiplier'],
                'reason' => $data->reason,
                'status' => OvertimeRequestStatus::PENDING,
            ]);
            $fresh = $row->fresh(['employee']);
            DB::afterCommit(function () use ($fresh, $userId): void {
                $this->events->dispatch(new OvertimeRequestSubmitted($fresh, $userId));
            });

            return $fresh;
        });
    }

    public function cancel(OvertimeRequest $request, int $actorUserId): OvertimeRequest
    {
        return DB::transaction(function () use ($request, $actorUserId): OvertimeRequest {
            $locked = $this->requests->lockForTenant((int) $request->tenant_id, (int) $request->id);
            if ($locked === null) {
                abort(404);
            }
            if ($locked->status !== OvertimeRequestStatus::PENDING) {
                return $locked;
            }

            $updated = $this->requests->update($locked, [
                'status' => OvertimeRequestStatus::CANCELLED,
            ]);
            DB::afterCommit(function () use ($updated, $actorUserId): void {
                $this->events->dispatch(new OvertimeRequestCancelled($updated, $actorUserId));
            });

            return $updated;
        });
    }

    /**
     * Preferred payroll input: approved OT minutes when any approved request exists
     * in the range; otherwise null so callers can use attendance overtime_minutes.
     */
    public function approvedOvertimeMinutesForEmployeeInRange(
        int $tenantId,
        int $employeeId,
        string $from,
        string $to,
    ): ?int {
        return $this->requests->approvedOvertimeMinutesForEmployeeInRange(
            $tenantId,
            $employeeId,
            $from,
            $to,
        );
    }

    private function assertNoBlockingDuplicate(int $tenantId, int $employeeId, string $date): void
    {
        if ($this->requests->existsBlockingForEmployeeDate($tenantId, $employeeId, $date)) {
            throw ValidationException::withMessages([
                'attendance_date' => __('overtime.errors.duplicate'),
            ]);
        }
    }
}
