<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\WorkHourDayContext;
use App\Dto\WorkHours\DayContextRow;
use App\Entity\User;
use App\Repository\Contract\AbsenceReadRepositoryInterface;
use App\Repository\Contract\EmployeeScopedRepositoryInterface;
use App\Repository\Contract\FormationReadRepositoryInterface;
use App\Service\Contracts\EmployeeContractResolver;
use App\Service\WorkHours\AbsenceSegmentsResolver;
use App\Service\WorkHours\HolidayVirtualHoursResolver;
use App\Service\WorkHours\WorkedHoursCreditPolicy;
use DateTimeImmutable;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

final readonly class WorkHourDayContextProvider implements ProviderInterface
{
    public function __construct(
        private Security $security,
        private RequestStack $requestStack,
        private EmployeeScopedRepositoryInterface $employeeRepository,
        private AbsenceReadRepositoryInterface $absenceRepository,
        private FormationReadRepositoryInterface $formationRepository,
        private EmployeeContractResolver $contractResolver,
        private AbsenceSegmentsResolver $absenceSegmentsResolver,
        private WorkedHoursCreditPolicy $workedHoursCreditPolicy,
        private HolidayVirtualHoursResolver $holidayVirtualHoursResolver,
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): WorkHourDayContext
    {
        $user = $this->security->getUser();
        // Endpoint protégé: on exige un utilisateur authentifié.
        if (!$user instanceof User) {
            throw new AccessDeniedHttpException('Authentication required.');
        }

        $workDate   = $this->resolveWorkDate();
        $employees  = $this->employeeRepository->findScoped($user);
        $absences   = $this->absenceRepository->findByDateAndEmployees($workDate, $employees);
        $formations = $this->formationRepository->findByDateAndEmployees($workDate, $employees);

        $rowsByEmployeeId = [];
        foreach ($employees as $employee) {
            $employeeId = $employee->getId();
            if (!$employeeId) {
                continue;
            }

            // On initialise toutes les lignes, même sans absence ce jour-là.
            $contract        = $this->contractResolver->resolveForEmployeeAndDate($employee, $workDate);
            $workDaysMinutes = $this->contractResolver->resolveWorkDaysMinutesForEmployeeAndDate($employee, $workDate);
            $contractNature  = null !== $contract
                ? $this->contractResolver->resolveNatureForEmployeeAndDate($employee, $workDate)->value
                : null;
            $rowsByEmployeeId[$employeeId] = new DayContextRow(
                employeeId: $employeeId,
                hasContractAtDate: null !== $contract,
                isDriverContract: $this->contractResolver->resolveIsDriverForEmployeeAndDate($employee, $workDate),
                virtualHolidayMinutes: $this->holidayVirtualHoursResolver->resolveVirtualCredit($contract, $workDate, false, $workDaysMinutes),
                contractNature: $contractNature,
                trackingMode: $contract?->getTrackingMode(),
                weeklyHours: $contract?->getWeeklyHours(),
                contractType: $contract?->getType()->value,
                contractName: $contract?->getName(),
                workDaysHours: $workDaysMinutes,
            );
        }

        $dateKey = $workDate->format('Y-m-d');
        foreach ($absences as $absence) {
            $employeeId = $absence->getEmployee()?->getId();
            // Ignore les absences orphelines ou hors scope utilisateur.
            if (!$employeeId || !isset($rowsByEmployeeId[$employeeId])) {
                continue;
            }

            [$absentMorning, $absentAfternoon] = $this->absenceSegmentsResolver->resolveForDate($absence, $dateKey);
            // Pas de segment absent sur ce jour: rien à injecter dans la ligne.
            if (!$absentMorning && !$absentAfternoon) {
                continue;
            }

            // Calcule le crédit d'heures selon la politique métier (type d'absence + contrat).
            $creditedMinutes       = $this->workedHoursCreditPolicy->computeCreditedMinutes($absence, $dateKey, $absentMorning, $absentAfternoon);
            $creditedPresenceUnits = $this->workedHoursCreditPolicy->computeCreditedPresenceUnits($absence, $dateKey, $absentMorning, $absentAfternoon);
            $rowsByEmployeeId[$employeeId]->addAbsence(
                label: $absence->getType()?->getLabel(),
                color: $absence->getType()?->getColor(),
                morning: $absentMorning,
                afternoon: $absentAfternoon,
                creditedMinutes: $creditedMinutes,
                creditedPresenceUnits: $creditedPresenceUnits
            );
        }

        foreach ($formations as $formation) {
            $employeeId = $formation->getEmployee()?->getId();
            if (!$employeeId || !isset($rowsByEmployeeId[$employeeId])) {
                continue;
            }
            $rowsByEmployeeId[$employeeId]->setFormation('Formation');
        }

        // If an absence is declared on the day, the absence dictates the hours credited
        // (via WorkedHoursCreditPolicy). The holiday virtual credit must not stack on top.
        foreach ($rowsByEmployeeId as $row) {
            if ($row->absentMorning || $row->absentAfternoon) {
                $row->virtualHolidayMinutes = 0;
            }
        }

        $response           = new WorkHourDayContext();
        $response->workDate = $dateKey;
        $response->rows     = array_map(
            static fn (DayContextRow $row): array => $row->toArray(),
            array_values($rowsByEmployeeId)
        );

        return $response;
    }

    private function resolveWorkDate(): DateTimeImmutable
    {
        $query = $this->requestStack->getCurrentRequest()?->query;
        $raw   = (string) ($query?->get('workDate') ?? '');

        // Sans paramètre, on cible la date du jour.
        if ('' === $raw) {
            return new DateTimeImmutable('today');
        }

        $date = DateTimeImmutable::createFromFormat('Y-m-d', $raw);
        // Validation stricte du format pour éviter les ambiguïtés de parsing.
        if (!$date || $date->format('Y-m-d') !== $raw) {
            throw new UnprocessableEntityHttpException('workDate must use Y-m-d format.');
        }

        return $date;
    }
}
