<?php

declare(strict_types=1);

namespace App\Module\Absence\Infrastructure\Mcp\Tool;

use App\Module\Absence\Application\Service\AbsenceBalanceService;
use App\Module\Absence\Domain\Enum\AbsenceStatus;
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
use App\Shared\Domain\Contract\UserInterface;
use App\Shared\Infrastructure\Mcp\Serializer;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Mcp\Capability\Attribute\McpTool;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

use function assert;
use function sprintf;

#[McpTool(name: 'review-absence-request', description: 'Approve or reject a PENDING absence request (admin). decision = "approve" or "reject"; rejectionReason is required when rejecting. Approving moves the days from pending to taken; rejecting releases the reserved days.')]
class ReviewAbsenceRequestTool
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly AbsenceRequestRepositoryInterface $requestRepository,
        private readonly AbsenceBalanceService $balanceService,
        private readonly Security $security,
    ) {}

    public function __invoke(int $id, string $decision, ?string $rejectionReason = null): string
    {
        if (!$this->security->isGranted('ROLE_ADMIN')) {
            throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
        }

        if (!in_array($decision, ['approve', 'reject'], true)) {
            throw new InvalidArgumentException('decision must be "approve" or "reject".');
        }

        $request = $this->requestRepository->findById($id);
        if (null === $request) {
            throw new InvalidArgumentException(sprintf('AbsenceRequest with ID %d not found.', $id));
        }

        if (AbsenceStatus::Pending !== $request->getStatus()) {
            throw new InvalidArgumentException('Only a pending request can be reviewed.');
        }

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

        if ('approve' === $decision) {
            // Never let an approval push the balance below zero (CP only).
            $available = $this->balanceService->availableForRequest($request);
            if (null !== $available && $request->getCountedDays() > $available + 1e-9) {
                throw new InvalidArgumentException(sprintf(
                    'Approving this request would put the balance below zero: %g day(s) requested but only %g available.',
                    $request->getCountedDays(),
                    $available,
                ));
            }

            $request->setStatus(AbsenceStatus::Approved);
            $request->setRejectionReason(null);
            $this->balanceService->applyApproval($request);
        } else {
            if (null === $rejectionReason || '' === trim($rejectionReason)) {
                throw new InvalidArgumentException('A reason is required when rejecting a request.');
            }
            $request->setStatus(AbsenceStatus::Rejected);
            $request->setRejectionReason($rejectionReason);
            $this->balanceService->release($request, false);
        }

        $request->setReviewedAt(new DateTimeImmutable());
        $request->setReviewedBy($admin);

        $this->entityManager->flush();

        return json_encode(Serializer::absenceRequest($request));
    }
}
