<?php

declare(strict_types=1);

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

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\Enum\HalfDay;
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
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 sprintf;

#[McpTool(name: 'create-absence-request', description: 'Create an absence request on behalf of an employee (userId). Validates active policy + no overlap, computes deducted working days, sets status=pending and reserves the days in the pending balance. type: cp|mariage_pacs|conge_parental|deces|maladie. Dates YYYY-MM-DD. halfDay (matin|apres_midi) on a boundary subtracts 0.5.')]
class CreateAbsenceRequestTool
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly UserRepositoryInterface $userRepository,
        private readonly AbsencePolicyRepositoryInterface $policyRepository,
        private readonly AbsenceRequestRepositoryInterface $requestRepository,
        private readonly AbsenceDayCalculator $calculator,
        private readonly AbsenceBalanceService $balanceService,
        private readonly Security $security,
    ) {}

    public function __invoke(
        int $userId,
        string $type,
        string $startDate,
        string $endDate,
        ?string $startHalfDay = null,
        ?string $endHalfDay = null,
        ?string $reason = null,
    ): string {
        if (!$this->security->isGranted('ROLE_USER')) {
            throw new AccessDeniedException('Access denied: ROLE_USER required.');
        }

        $user = $this->userRepository->findById($userId)
            ?? throw new InvalidArgumentException(sprintf('User with ID %d not found.', $userId));

        $typeEnum = AbsenceType::tryFrom($type)
            ?? throw new InvalidArgumentException(sprintf('Unknown absence type "%s".', $type));

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

        $startHd = null !== $startHalfDay
            ? (HalfDay::tryFrom($startHalfDay) ?? throw new InvalidArgumentException(sprintf('Unknown half day "%s".', $startHalfDay)))
            : null;
        $endHd = null !== $endHalfDay
            ? (HalfDay::tryFrom($endHalfDay) ?? throw new InvalidArgumentException(sprintf('Unknown half day "%s".', $endHalfDay)))
            : null;

        $policy = $this->policyRepository->findOneByType($typeEnum);
        if (null === $policy || !$policy->isActive()) {
            throw new InvalidArgumentException('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 === $typeEnum && '' === trim((string) $reason)) {
            throw new InvalidArgumentException('A reason (relationship to the deceased) is required for bereavement leave.');
        }

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

        $countedDays = $this->calculator->countWorkingDays($start, $end, $startHd, $endHd, $policy->isCountWorkingDaysOnly());
        if ($countedDays <= 0.0) {
            throw new InvalidArgumentException('The selected range contains no working day.');
        }

        $request = new AbsenceRequest();
        $request->setUser($user);
        $request->setType($typeEnum);
        $request->setStartDate($start);
        $request->setEndDate($end);
        $request->setStartHalfDay($startHd);
        $request->setEndHalfDay($endHd);
        $request->setReason($reason);
        $request->setCountedDays($countedDays);
        $request->setStatus(AbsenceStatus::Pending);
        $request->setRejectionReason(null);
        $request->setCreatedAt(new DateTimeImmutable());

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

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