<?php

declare(strict_types=1);

namespace App\Module\Absence\Infrastructure\Controller;

use App\Module\Absence\Application\Service\AbsenceBalanceService;
use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Absence\Domain\Enum\HalfDay;
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Dry-run endpoint for the "new request" form: returns the number of deducted
 * days and the projected balance without creating anything. Required because
 * public holidays are computed server-side.
 */
class AbsencePreviewController extends AbstractController
{
    public function __construct(
        private readonly Security $security,
        private readonly AbsenceDayCalculator $calculator,
        private readonly AbsencePolicyRepositoryInterface $policyRepository,
        private readonly AbsenceBalanceRepositoryInterface $balanceRepository,
        private readonly AbsenceBalanceService $balanceService,
    ) {}

    #[Route('/api/absence_requests/preview', name: 'absence_request_preview', methods: ['POST'], priority: 1)]
    #[IsGranted('ROLE_USER')]
    public function __invoke(Request $request): JsonResponse
    {
        /** @var array<string, mixed> $payload */
        $payload = json_decode($request->getContent(), true) ?? [];

        $type = AbsenceType::tryFrom((string) ($payload['type'] ?? ''));
        if (null === $type) {
            throw new UnprocessableEntityHttpException('Unknown absence type.');
        }

        $startRaw = (string) ($payload['startDate'] ?? '');
        $endRaw   = (string) ($payload['endDate'] ?? '');
        if ('' === $startRaw || '' === $endRaw) {
            throw new UnprocessableEntityHttpException('Start date and end date are required.');
        }

        $start = new DateTimeImmutable($startRaw);
        $end   = new DateTimeImmutable($endRaw);
        if ($end < $start) {
            throw new UnprocessableEntityHttpException('End date must be on or after start date.');
        }

        $policy          = $this->policyRepository->findOneByType($type);
        $workingDaysOnly = $policy?->isCountWorkingDaysOnly() ?? true;

        $countedDays = $this->calculator->countWorkingDays(
            $start,
            $end,
            isset($payload['startHalfDay']) ? HalfDay::tryFrom((string) $payload['startHalfDay']) : null,
            isset($payload['endHalfDay']) ? HalfDay::tryFrom((string) $payload['endHalfDay']) : null,
            $workingDaysOnly,
        );

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

        $available          = null;
        $projectedAvailable = null;
        $period             = null;

        if ($type->decrementsBalance()) {
            $period             = $this->balanceService->periodFor($user, $type, $start);
            $balance            = $this->balanceRepository->findOneForPeriod($user, $type, $period);
            $available          = $balance?->getAvailable() ?? 0.0;
            $projectedAvailable = $available - $countedDays;
        }

        return $this->json([
            'countedDays'           => $countedDays,
            'period'                => $period,
            'available'             => $available,
            'projectedAvailable'    => $projectedAvailable,
            'justificationRequired' => $policy?->isJustificationRequired() ?? false,
        ]);
    }
}
