<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\MessageHandler;

use Ferienpass\CoreBundle\ApplicationSystem\Lotting\Assignment;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Message\LotOffer as LotOfferCommand;
use Ferienpass\CoreBundle\Repository\AttendanceStashRepository;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;

#[AsMessageHandler(bus: 'command.bus')]
class LotOffer
{
    public function __construct(private readonly OfferRepositoryInterface $offers, private readonly Assignment $assignment, private readonly LockFactory $lockFactory, private readonly AttendanceStashRepository $stashRepository)
    {
    }

    public function __invoke(LotOfferCommand $command): void
    {
        /** @var OfferInterface $offer */
        $offer = $this->offers->findOneBy(['uuid' => $command->getOfferId()]);
        if (!$offer instanceof OfferInterface) {
            return;
        }

        $edition = $offer->getEdition();
        if (null === $edition) {
            return;
        }

        $stash = $this->stashRepository->findOrCreateDefault();

        $lock = $this->lockFactory->createLock('lotting.'.$edition->getUuid(), ttl: 300, autoRelease: false);

        // Use blocking so that standalone lotting jobs for the same edition do not overlap.
        if (!$lock->acquire(true)) {
            throw new RecoverableMessageHandlingException();
        }

        try {
            $lock->refresh();

            // 0 means reject all
            $maxWaitlist = 0;
            if ($command->shouldUseWaitlist()) {
                // can become null (meaning to put all on waitlist)
                $maxWaitlist = $edition->findUpcomingTask('application_system', 'firstcome')?->getMaxWaitlist();
            }

            $this->assignment->drawForOffer($offer, $maxWaitlist, $stash);
        } finally {
            $lock->release();
        }
    }
}
