<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\WorkHourValidationStatus;
use App\Entity\Employee;
use App\Entity\User;
use App\Repository\Contract\EmployeeScopedRepositoryInterface;
use App\Repository\Contract\WorkHourReadRepositoryInterface;
use App\Service\Contracts\EmployeeContractResolver;
use DateTimeImmutable;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * Statut de validation par jour pour le calendrier de la vue Jour (écran Heures).
 *
 * Un jour est « entièrement validé » (peint en vert côté front) ssi, dans le
 * périmètre de l'utilisateur (admin = tous les sites, chef de site = ses sites) :
 *   - il porte au moins une ligne WorkHour de non-conducteur ce jour-là, ET
 *   - aucune de ces lignes n'est en attente de validation (isValid=false).
 *
 * Par défaut les conducteurs sont exclus (écran Heures). Avec `?driver=1`, le filtre
 * s'inverse et seuls les conducteurs sont pris en compte (écran Heures Conducteurs).
 * Le filtre sites de l'écran est volontairement ignoré :
 * le statut reflète tout le périmètre (objectif RH : repérer le moindre jour
 * incomplet, où qu'il soit). Un jour sans aucune ligne reste neutre (absent de
 * la liste).
 */
final readonly class WorkHourValidationStatusProvider implements ProviderInterface
{
    /** Garde-fou : borne la plage demandée pour éviter une requête démesurée. */
    private const int MAX_RANGE_DAYS = 366;

    public function __construct(
        private Security $security,
        private RequestStack $requestStack,
        private EmployeeScopedRepositoryInterface $employeeRepository,
        private WorkHourReadRepositoryInterface $workHourRepository,
        private EmployeeContractResolver $contractResolver,
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): WorkHourValidationStatus
    {
        $user = $this->security->getUser();
        if (!$user instanceof User) {
            throw new AccessDeniedHttpException('Authentication required.');
        }

        [$from, $to] = $this->resolveRange();
        // ?driver=1 → ne garder que les conducteurs (écran Heures Conducteurs) ;
        // défaut → ne garder que les non-conducteurs (écran Heures).
        $driverOnly = filter_var(
            $this->requestStack->getCurrentRequest()?->query->get('driver'),
            FILTER_VALIDATE_BOOLEAN
        );

        $employees = $this->employeeRepository->findScoped($user);
        $workHours = $this->workHourRepository->findByDateRangeAndEmployees($from, $to, $employees);

        // Agrégation par jour : total = lignes non-conducteur, pending = lignes isValid=false.
        /** @var array<string, array{total:int, pending:int}> $byDate */
        $byDate = [];
        // Mémoïsation de la résolution conducteur par (employé, jour) : un même
        // couple peut revenir et resolveIsDriver... interroge la BDD.
        $driverCache = [];

        foreach ($workHours as $workHour) {
            $employee = $workHour->getEmployee();
            if (!$employee instanceof Employee) {
                continue;
            }
            $date    = DateTimeImmutable::createFromInterface($workHour->getWorkDate());
            $dateKey = $date->format('Y-m-d');

            $cacheKey = $employee->getId().'|'.$dateKey;
            $isDriver = $driverCache[$cacheKey]
                ??= $this->contractResolver->resolveIsDriverForEmployeeAndDate($employee, $date);
            if ($isDriver !== $driverOnly) {
                continue;
            }

            $bucket = &$byDate[$dateKey];
            $bucket ??= ['total' => 0, 'pending' => 0];
            ++$bucket['total'];
            if (!$workHour->isValid()) {
                ++$bucket['pending'];
            }
            unset($bucket);
        }

        $validatedDays = [];
        foreach ($byDate as $dateKey => $counts) {
            if ($counts['total'] > 0 && 0 === $counts['pending']) {
                $validatedDays[] = $dateKey;
            }
        }
        sort($validatedDays);

        $response                = new WorkHourValidationStatus();
        $response->from          = $from->format('Y-m-d');
        $response->to            = $to->format('Y-m-d');
        $response->validatedDays = $validatedDays;

        return $response;
    }

    /**
     * @return array{0: DateTimeImmutable, 1: DateTimeImmutable}
     */
    private function resolveRange(): array
    {
        $query = $this->requestStack->getCurrentRequest()?->query;
        $from  = $this->parseDate((string) ($query?->get('from') ?? ''), 'from');
        $to    = $this->parseDate((string) ($query?->get('to') ?? ''), 'to');

        if ($from > $to) {
            throw new UnprocessableEntityHttpException('from must be before or equal to to.');
        }
        if ($from->diff($to)->days > self::MAX_RANGE_DAYS) {
            throw new UnprocessableEntityHttpException(sprintf('Range must not exceed %d days.', self::MAX_RANGE_DAYS));
        }

        return [$from, $to];
    }

    private function parseDate(string $raw, string $field): DateTimeImmutable
    {
        $date = DateTimeImmutable::createFromFormat('Y-m-d', $raw);
        if (!$date || $date->format('Y-m-d') !== $raw) {
            throw new UnprocessableEntityHttpException(sprintf('%s must use Y-m-d format.', $field));
        }

        // Normalise à minuit pour comparer des jours, pas des instants.
        return $date->setTime(0, 0);
    }
}
