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

use Ferienpass\CmsBundle\Application\AbstractParticipantExclusionReason;
use Ferienpass\CmsBundle\Application\ExclusionReasons\AgeInvalid;
use Ferienpass\CmsBundle\Application\ExclusionReasons\DayLimitReached;
use Ferienpass\CmsBundle\Application\ExclusionReasons\LimitReached;
use Ferienpass\CmsBundle\Application\ExclusionReasons\NoAccessCode;
use Ferienpass\CmsBundle\Application\ExclusionReasons\OverlappingDates;
use Ferienpass\CmsBundle\Application\ExclusionReasons\Unconfirmed;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\Participant\ParticipantInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Translation\TranslatableMessage;

final class AbstractParticipantExclusionReasonTest extends TestCase
{
    public function testGetOffer(): void
    {
        $offer = $this->createMock(OfferInterface::class);
        $participant = $this->createMock(ParticipantInterface::class);
        $message = new TranslatableMessage('key');
        $reason = new AgeInvalid($offer, $participant, $message);

        self::assertSame($offer, $reason->getOffer());
    }

    public function testGetParticipant(): void
    {
        $offer = $this->createMock(OfferInterface::class);
        $participant = $this->createMock(ParticipantInterface::class);
        $message = new TranslatableMessage('key');
        $reason = new AgeInvalid($offer, $participant, $message);

        self::assertSame($participant, $reason->getParticipant());
    }

    public function testGetUserMessage(): void
    {
        $offer = $this->createMock(OfferInterface::class);
        $participant = $this->createMock(ParticipantInterface::class);
        $message = new TranslatableMessage('key');
        $reason = new AgeInvalid($offer, $participant, $message);

        self::assertSame($message, $reason->getUserMessage());
    }

    public function testIsException(): void
    {
        $reason = $this->makeReason(AgeInvalid::class);

        self::assertInstanceOf(\Exception::class, $reason);
    }

    /**
     * The exception message must be taken from the message id, never by casting the
     * TranslatableMessage: __toString() is deprecated since symfony/translation 7.4
     * and removed in 8.0, where the cast is a TypeError.
     */
    public function testExceptionMessageIsTheMessageIdWithoutStringifying(): void
    {
        $deprecations = [];
        set_error_handler(static function (int $type, string $message) use (&$deprecations): bool {
            $deprecations[] = $message;

            return true;
        }, \E_USER_DEPRECATED);

        try {
            $reason = $this->makeReason(AgeInvalid::class);
        } finally {
            restore_error_handler();
        }

        self::assertSame([], $deprecations);
        self::assertSame('some.key', $reason->getMessage());
    }

    public function testGetReasonForAgeInvalid(): void
    {
        $reason = $this->makeReason(AgeInvalid::class);

        self::assertSame('age_invalid', $reason->getReason());
    }

    public function testGetReasonForLimitReached(): void
    {
        $reason = $this->makeReason(LimitReached::class);

        self::assertSame('limit_reached', $reason->getReason());
    }

    public function testGetReasonForDayLimitReached(): void
    {
        $reason = $this->makeReason(DayLimitReached::class);

        self::assertSame('day_limit_reached', $reason->getReason());
    }

    public function testGetReasonForNoAccessCode(): void
    {
        $reason = $this->makeReason(NoAccessCode::class);

        self::assertSame('no_access_code', $reason->getReason());
    }

    public function testGetReasonForOverlappingDates(): void
    {
        $reason = $this->makeReason(OverlappingDates::class);

        self::assertSame('overlapping_dates', $reason->getReason());
    }

    public function testGetReasonForUnconfirmed(): void
    {
        $reason = $this->makeReason(Unconfirmed::class);

        self::assertSame('unconfirmed', $reason->getReason());
    }

    private function makeReason(string $class): AbstractParticipantExclusionReason
    {
        $offer = $this->createMock(OfferInterface::class);
        $participant = $this->createMock(ParticipantInterface::class);
        $message = new TranslatableMessage('some.key');

        return new $class($offer, $participant, $message);
    }
}
