<?php

declare(strict_types=1);

namespace App\Module\Absence\Infrastructure\ApiPlatform\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Absence\Application\Service\AbsenceBalanceService;
use App\Module\Absence\Domain\Entity\AbsenceRequest;
use App\Module\Absence\Domain\Enum\AbsenceStatus;
use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * Handles creation of an absence request: computes the deducted days, enforces
 * the overlap rule, and reserves the days in the employee's pending balance.
 *
 * @implements ProcessorInterface<AbsenceRequest, AbsenceRequest>
 */
final readonly class AbsenceRequestProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private Security $security,
        private AbsenceDayCalculator $calculator,
        private AbsencePolicyRepositoryInterface $policyRepository,
        private AbsenceRequestRepositoryInterface $requestRepository,
        private AbsenceBalanceService $balanceService,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): AbsenceRequest
    {
        assert($data instanceof AbsenceRequest);

        $user = $this->security->getUser();
        assert($user instanceof UserInterface);

        $type      = $data->getType();
        $startDate = $data->getStartDate();
        $endDate   = $data->getEndDate();

        if (null === $type || null === $startDate || null === $endDate) {
            throw new UnprocessableEntityHttpException('Type, start date and end date are required.');
        }

        if ($endDate < $startDate) {
            throw new UnprocessableEntityHttpException('End date must be on or after start date.');
        }

        $policy = $this->policyRepository->findOneByType($type);
        if (null === $policy || !$policy->isActive()) {
            throw new UnprocessableEntityHttpException('This absence type is not available.');
        }

        // Bereavement has no fixed entitlement: the relationship to the deceased
        // drives the legal number of days, so the reason is mandatory.
        if (AbsenceType::Bereavement === $type && '' === trim((string) $data->getReason())) {
            throw new UnprocessableEntityHttpException('A reason (relationship to the deceased) is required for bereavement leave.');
        }

        if ($this->requestRepository->hasOverlap($user, $startDate, $endDate)) {
            throw new ConflictHttpException('This request overlaps an existing absence.');
        }

        $countedDays = $this->calculator->countWorkingDays(
            $startDate,
            $endDate,
            $data->getStartHalfDay(),
            $data->getEndHalfDay(),
            $policy->isCountWorkingDaysOnly(),
        );

        if ($countedDays <= 0.0) {
            throw new UnprocessableEntityHttpException('The selected range contains no working day.');
        }

        $data->setUser($user);
        $data->setCountedDays($countedDays);
        $data->setStatus(AbsenceStatus::Pending);
        $data->setRejectionReason(null);
        $data->setCreatedAt(new DateTimeImmutable());

        $this->entityManager->persist($data);
        $this->balanceService->reservePending($data);
        $this->entityManager->flush();

        return $data;
    }
}
