<?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\Facade;

use Doctrine\ORM\QueryBuilder;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Edition;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Facade\PaymentsFacade;
use Ferienpass\CoreBundle\Fixtures\Doctrine\OrmTestCase;

/**
 * The DQL that keeps seasons without a payment system out of the payment lists,
 * executed against a real schema.
 */
final class PaymentsFacadeUsePaymentsTest extends OrmTestCase
{
    public function testAddWhereLogicKeepsAttendancesOfAPayingSeason(): void
    {
        $attendance = $this->createAttendance($this->createEdition());

        self::assertSame([$attendance], $this->runAddWhereLogic());
    }

    public function testAddWhereLogicDropsAttendancesOfANonPayingSeason(): void
    {
        $edition = $this->createEdition();
        $edition->setUsePayments(false);

        $this->createAttendance($edition);

        self::assertSame([], $this->runAddWhereLogic());
    }

    public function testAddWhereLogicKeepsAttendancesWithoutASeason(): void
    {
        // An offer that lost its season still owes what it owes.
        $attendance = $this->createAttendance(null);

        self::assertSame([$attendance], $this->runAddWhereLogic());
    }

    public function testAddWhereLogicKeepsOnlyThePayingSeasonWhenBothExist(): void
    {
        $paying = $this->createEdition();

        $notPaying = $this->createEdition();
        $notPaying->setUsePayments(false);

        $attendance = $this->createAttendance($paying);
        $this->createAttendance($notPaying);

        self::assertSame([$attendance], $this->runAddWhereLogic());
    }

    public function testAddUsePaymentsLogicIgnoresPaidAndPayable(): void
    {
        // The season condition stands on its own: a settled attendance of a
        // paying season is dropped by addWhereLogic but kept by this one.
        $attendance = $this->createAttendance($this->createEdition());
        $attendance->setPaid();
        $attendance->setPayable(false);

        $this->em->flush();

        $qb = $this->attendancesQuery();
        PaymentsFacade::addUsePaymentsLogic($qb);

        self::assertSame([$attendance], $qb->getQuery()->getResult());
        self::assertSame([], $this->runAddWhereLogic());
    }

    /**
     * @return list<Attendance>
     */
    private function runAddWhereLogic(): array
    {
        $qb = $this->attendancesQuery();
        PaymentsFacade::addWhereLogic($qb);

        return $qb->getQuery()->getResult();
    }

    private function attendancesQuery(): QueryBuilder
    {
        return $this->em->createQueryBuilder()
            ->select('attendance')
            ->from(Attendance::class, 'attendance')
            ->orderBy('attendance.id', 'ASC')
        ;
    }

    private function createAttendance(?Edition $edition): Attendance
    {
        $offer = $this->createOffer(edition: $edition);
        \assert($offer instanceof OfferInterface);

        $attendance = new Attendance($offer, $this->createParticipant());
        // What the confirm transition leaves behind for a fee-bearing offer.
        $attendance->setPayable(true);

        $this->em->persist($attendance);
        $this->em->flush();

        return $attendance;
    }
}
