<?php

namespace App\Modules\HR\Shifts\Services;

use App\Modules\HR\Employees\Models\Employee;
use App\Modules\HR\Shifts\Events\ShiftAssigned;
use App\Modules\HR\Shifts\Events\ShiftAssignmentEnded;
use App\Modules\HR\Shifts\Models\EmployeeShiftAssignment;
use App\Modules\HR\Shifts\Repositories\EmployeeShiftAssignmentRepository;
use App\Modules\HR\Shifts\Repositories\ShiftRepository;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class EmployeeShiftAssignmentService
{
    public function __construct(
        private readonly EmployeeShiftAssignmentRepository $assignments,
        private readonly ShiftRepository $shifts,
        private readonly Dispatcher $events,
    ) {}

    public function assign(
        int $tenantId,
        int $employeeId,
        int $shiftId,
        int $actorUserId,
        ?CarbonImmutable $startsAt = null,
    ): EmployeeShiftAssignment {
        return DB::transaction(function () use ($tenantId, $employeeId, $shiftId, $actorUserId, $startsAt): EmployeeShiftAssignment {
            $employee = Employee::query()
                ->where('tenant_id', $tenantId)
                ->whereKey($employeeId)
                ->lockForUpdate()
                ->firstOrFail();
            $shift = $this->shifts->findForTenant($tenantId, $shiftId);
            if ($shift === null || ! $shift->is_active) {
                abort(404);
            }
            $start = $startsAt ?? CarbonImmutable::now();
            foreach ($this->assignments->openRowsForEmployee($tenantId, $employeeId) as $row) {
                $this->assignments->closeAssignment($row, $start);
            }
            $row = $this->assignments->create([
                'tenant_id' => $tenantId,
                'employee_id' => $employee->id,
                'shift_id' => $shift->id,
                'starts_at' => $start,
                'ends_at' => null,
                'is_active' => $start->lte(CarbonImmutable::now()),
                'assigned_by' => $actorUserId,
            ]);
            $fresh = $row->fresh(['employee', 'shift']);
            DB::afterCommit(fn () => $this->events->dispatch(new ShiftAssigned($fresh)));

            return $fresh;
        });
    }

    public function end(EmployeeShiftAssignment $assignment, int $actorUserId): EmployeeShiftAssignment
    {
        return DB::transaction(function () use ($assignment, $actorUserId): EmployeeShiftAssignment {
            $locked = $this->assignments->lockForTenant(
                (int) $assignment->tenant_id,
                (int) $assignment->id,
            );
            if ($locked === null) {
                abort(404);
            }
            if (! $locked->is_active && $locked->ends_at !== null) {
                throw ValidationException::withMessages([
                    'assignment' => [__('shifts.validation.already_ended')],
                ]);
            }
            $this->assignments->closeAssignment($locked, CarbonImmutable::now());
            $fresh = $locked->fresh(['employee', 'shift']);
            DB::afterCommit(fn () => $this->events->dispatch(
                new ShiftAssignmentEnded($fresh, $actorUserId),
            ));

            return $fresh;
        });
    }
}
