<?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 Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;

/**
 * Cancellation of an absence request. An employee may cancel their own PENDING
 * request; an admin may additionally cancel an APPROVED one, which credits the
 * deducted days back to the balance.
 *
 * @implements ProcessorInterface<AbsenceRequest, AbsenceRequest>
 */
final readonly class AbsenceCancelProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private Security $security,
        private AbsenceBalanceService $balanceService,
    ) {}

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

        // Cancellation carries no payload: keep the persisted content intact.
        $previous = $context['previous_data'] ?? null;
        if ($previous instanceof AbsenceRequest) {
            $data->setType($previous->getType());
            $data->setStartDate($previous->getStartDate());
            $data->setEndDate($previous->getEndDate());
            $data->setStartHalfDay($previous->getStartHalfDay());
            $data->setEndHalfDay($previous->getEndHalfDay());
            $data->setReason($previous->getReason());
            $data->setCountedDays($previous->getCountedDays());
            $data->setStatus($previous->getStatus());
        }

        $user    = $this->security->getUser();
        $isAdmin = $this->security->isGranted('ROLE_ADMIN');
        $status  = $data->getStatus();

        // Authorize before mutating the balance: an employee may only cancel
        // their own request (admins can cancel any).
        if (!$isAdmin && $data->getUser() !== $user) {
            throw new AccessDeniedHttpException('You can only cancel your own requests.');
        }

        if (AbsenceStatus::Pending === $status) {
            $this->balanceService->release($data, false);
        } elseif (AbsenceStatus::Approved === $status) {
            if (!$isAdmin) {
                throw new AccessDeniedHttpException('Only an admin can cancel an approved request.');
            }
            $this->balanceService->release($data, true);
        } else {
            throw new ConflictHttpException('This request can no longer be cancelled.');
        }

        $data->setStatus(AbsenceStatus::Cancelled);

        $this->entityManager->flush();

        return $data;
    }
}
