<?php

declare(strict_types=1);

namespace App\Service\WorkHours;

use App\ApiResource\WorkHourBulkValidationResult;
use App\Entity\User;
use App\Entity\WorkHour;
use App\Repository\EmployeeRepository;
use App\Repository\WorkHourRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final readonly class WorkHourBulkValidationExecutor
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private EmployeeRepository $employeeRepository,
        private WorkHourRepository $workHourRepository,
    ) {}

    /**
     * @param list<mixed>                    $employeeIds
     * @param callable(?WorkHour, int): bool $shouldSkip
     * @param callable(WorkHour, int): void  $applyUpdate
     */
    public function execute(
        User $user,
        string $workDateValue,
        array $employeeIds,
        callable $shouldSkip,
        callable $applyUpdate
    ): WorkHourBulkValidationResult {
        $workDate = DateTimeImmutable::createFromFormat('Y-m-d', $workDateValue);
        if (!$workDate || $workDate->format('Y-m-d') !== $workDateValue) {
            throw new UnprocessableEntityHttpException('workDate must use Y-m-d format.');
        }

        $normalizedEmployeeIds = $this->normalizeEmployeeIds($employeeIds);
        if ([] === $normalizedEmployeeIds) {
            throw new UnprocessableEntityHttpException('employeeIds must contain at least one employee.');
        }

        $employeesById = $this->employeeRepository->findAccessibleByIds($normalizedEmployeeIds, $user);
        if (count($employeesById) !== count($normalizedEmployeeIds)) {
            throw new AccessDeniedHttpException('At least one employee is unknown or outside your scope.');
        }

        $existingByEmployeeId = $this->workHourRepository
            ->findByDateAndEmployeesIndexedByEmployeeId($workDate, array_values($employeesById))
        ;

        $result            = new WorkHourBulkValidationResult();
        $result->requested = count($normalizedEmployeeIds);

        foreach ($normalizedEmployeeIds as $employeeId) {
            $workHour = $existingByEmployeeId[$employeeId] ?? null;
            if (null === $workHour || $shouldSkip($workHour, $employeeId)) {
                ++$result->skipped;
                $result->skippedEmployeeIds[] = $employeeId;

                continue;
            }

            $applyUpdate($workHour, $employeeId);
            ++$result->updated;
            $result->updatedEmployeeIds[] = $employeeId;
        }

        if ($result->updated > 0) {
            $this->entityManager->flush();
        }

        return $result;
    }

    /**
     * @param list<mixed> $employeeIds
     *
     * @return list<int>
     */
    private function normalizeEmployeeIds(array $employeeIds): array
    {
        $normalized = [];
        foreach ($employeeIds as $index => $rawId) {
            $employeeId = (int) $rawId;
            if ($employeeId <= 0) {
                throw new UnprocessableEntityHttpException(sprintf('employeeIds[%d] must be a positive integer.', $index));
            }

            if (isset($normalized[$employeeId])) {
                throw new UnprocessableEntityHttpException(sprintf('Employee %d appears multiple times in payload.', $employeeId));
            }

            $normalized[$employeeId] = $employeeId;
        }

        return array_values($normalized);
    }
}
