<?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\Tests\Entity;

use Doctrine\ORM\PersistentCollection;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Enum\OfferStatus;
use Ferienpass\CoreBundle\Fixtures\Doctrine\OrmTestCase;

/**
 * {@see \Ferienpass\CoreBundle\Entity\Offer\Base::getVacancies()} runs once per card
 * in the offer list, so it must stay a COUNT rather than hydrate every attendance.
 */
final class OfferVacanciesTest extends OrmTestCase
{
    public function testCountsOccupiedSpotsWithoutLoadingTheAttendances(): void
    {
        $offer = $this->offerWithAttendances(max: 10);

        $attendances = $offer->getAttendances();
        self::assertInstanceOf(PersistentCollection::class, $attendances);
        self::assertFalse($attendances->isInitialized(), 'the collection starts out unloaded');

        self::assertSame(7, $offer->getVacancies());

        self::assertFalse(
            $attendances->isInitialized(),
            'getVacancies() hydrated the attendances — EXTRA_LAZY was lost, and the offer list now loads every attendance of every card',
        );
    }

    /**
     * Without a limit there is nothing to count, so the collection must not be
     * touched at all.
     */
    public function testAnUnlimitedOfferDoesNotQueryTheAttendancesAtAll(): void
    {
        $offer = $this->offerWithAttendances(max: null);

        self::assertNull($offer->getVacancies());
        self::assertFalse($offer->getAttendances()->isInitialized());
    }

    /**
     * A collection already in memory answers from memory — the same rule has to
     * hold there, or the offer's own page would disagree with the list.
     */
    public function testCountsTheSameOnAnAlreadyLoadedCollection(): void
    {
        $offer = $this->offerWithAttendances(max: 10);

        $offer->getAttendances()->toArray();
        self::assertTrue($offer->getAttendances()->isInitialized());

        self::assertSame(7, $offer->getVacancies());
    }

    private function offerWithAttendances(?int $max): OfferInterface
    {
        $offer = $this->createOffer([OfferStatus::PUBLISHED]);
        $offer->setMaxParticipants($max);

        // 2 confirmed + 1 waitlisted occupy a spot; the rest released theirs.
        foreach ([AttendanceStatus::CONFIRMED, AttendanceStatus::CONFIRMED, AttendanceStatus::WAITLISTED, AttendanceStatus::WITHDRAWN, AttendanceStatus::REJECTED] as $status) {
            $attendance = new Attendance($offer, null);
            $attendance->setStatus($status);

            $offer->addAttendance($attendance);
            $this->em->persist($attendance);
        }

        $this->em->flush();
        $this->em->clear();

        return $this->em->find(OfferInterface::class, $offer->getId());
    }
}
