<?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 Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Facade\PaymentsFacade;
use Ferienpass\CoreBundle\Fixtures\Doctrine\OrmTestCase;

/**
 * The rule that decides which attendances may be billed at all, executed
 * against a real schema. "Anmeldungen festschreiben" bills staged confirmations
 * with a bulk UPDATE ({@see \Ferienpass\CoreBundle\MessageHandler\CommitAttendances})
 * that bypasses the workflow listeners, so it excludes the exempt attendances
 * through this predicate instead.
 *
 * The handler itself cannot be driven end-to-end here: it selects the
 * attendances by their BINARY(16) uuid, and SQLite stores what the harness
 * writes as TEXT, so no bound uuid ever matches.
 */
final class PaymentsFacadeUnbillableTest extends OrmTestCase
{
    public function testAFeeBearingOnlineOfferIsBillable(): void
    {
        $this->createAttendance(fee: 500);

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

    public function testAFreeOfferIsNotBillable(): void
    {
        $withoutFee = $this->createAttendance(fee: null);
        $withZeroFee = $this->createAttendance(fee: 0);

        self::assertSame([$withoutFee, $withZeroFee], $this->unbillableAttendances());
    }

    public function testAnOfferPaidOfflineIsNotBillable(): void
    {
        $attendance = $this->createAttendance(fee: 500, onlinePayment: false);

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

    public function testOnlyTheExemptAttendancesAreHeldBack(): void
    {
        $this->createAttendance(fee: 500);
        $free = $this->createAttendance(fee: 0);
        $this->createAttendance(fee: 1);
        $offline = $this->createAttendance(fee: 500, onlinePayment: false);

        self::assertSame([$free, $offline], $this->unbillableAttendances());
    }

    /**
     * The commit embeds the predicate as a subquery over its own `a1` alias,
     * from which the helper derives the alias it joins the offer under.
     */
    public function testThePredicateAlsoParsesUnderTheCommitsAlias(): void
    {
        $attendance = $this->createAttendance(fee: null);

        $qb = $this->em->createQueryBuilder()
            ->select('a1.uuid')
            ->from(Attendance::class, 'a1')
        ;

        PaymentsFacade::addUnbillableLogic($qb, 'a1');

        $uuids = array_map(static fn (array $row): string => (string) $row['uuid'], $qb->getQuery()->getResult());

        self::assertSame([(string) $attendance->getUuid()], $uuids);
    }

    /**
     * @return list<Attendance>
     */
    private function unbillableAttendances(): array
    {
        $qb = $this->em->createQueryBuilder()
            ->select('attendance')
            ->from(Attendance::class, 'attendance')
            ->orderBy('attendance.id', 'ASC')
        ;

        PaymentsFacade::addUnbillableLogic($qb);

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

    private function createAttendance(?int $fee, bool $onlinePayment = true): Attendance
    {
        $offer = $this->createOffer();
        \assert($offer instanceof OfferInterface);
        $offer->setFee($fee);
        $offer->setDisableOnlinePayment(!$onlinePayment);

        $attendance = new Attendance($offer, $this->createParticipant());

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

        return $attendance;
    }
}
