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

use Contao\CoreBundle\Cron\Cron;
use Ferienpass\CoreBundle\Cron\NotifyOfferComplete;
use Ferienpass\CoreBundle\Entity\MessengerLog;
use Ferienpass\CoreBundle\Entity\Offer;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Enum\OfferStatus;
use Ferienpass\CoreBundle\Fixtures\Doctrine\OrmTestCase;
use Ferienpass\CoreBundle\Fixtures\Messenger\CollectingMessageBus;
use Ferienpass\CoreBundle\Message\OfferCompleteNotify;
use Ferienpass\CoreBundle\Repository\OfferRepositoryInterface;
use Symfony\Component\Workflow\WorkflowInterface;

final class NotifyOfferCompleteTest extends OrmTestCase
{
    private CollectingMessageBus $bus;

    protected function setUp(): void
    {
        parent::setUp();

        $this->bus = new CollectingMessageBus();
    }

    public function testWebScopeReturnsEarly(): void
    {
        $this->eligibleOffer();
        $this->em->flush();

        $this->cron()(Cron::SCOPE_WEB);

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

    public function testNotifiesOnceTheEventIsOver(): void
    {
        $offer = $this->eligibleOffer();
        $this->em->flush();

        $this->cron()(Cron::SCOPE_CLI);

        self::assertCount(1, $this->bus->messages);
        self::assertInstanceOf(OfferCompleteNotify::class, $this->bus->messages[0]);
        self::assertSame((string) $offer->getUuid(), $this->bus->messages[0]->getOfferId());
    }

    /**
     * The offer is not transitioned to completed by this cron — the host has to answer
     * the survey first — so it keeps matching the query on every later run. Only the
     * messenger log stops the reminder from going out daily, forever.
     */
    public function testDoesNotNotifyAgainOnceLogged(): void
    {
        $offer = $this->eligibleOffer();
        $this->em->flush();

        $this->cron()(Cron::SCOPE_CLI);
        self::assertCount(1, $this->bus->messages);

        $this->log(OfferCompleteNotify::class, $offer);
        $this->bus->reset();

        $this->cron()(Cron::SCOPE_CLI);

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

    public function testAnUnrelatedLogDoesNotSuppressTheNotification(): void
    {
        $offer = $this->eligibleOffer();
        $this->em->flush();

        $this->log('Some\Other\Message', $offer);

        $this->cron()(Cron::SCOPE_CLI);

        self::assertCount(1, $this->bus->messages);
    }

    public function testSkipsOffersTheWorkflowCannotComplete(): void
    {
        $this->eligibleOffer();
        $this->em->flush();

        $this->cron(canComplete: false)(Cron::SCOPE_CLI);

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

    public function testSkipsArchivedEditions(): void
    {
        $offer = $this->eligibleOffer();
        $offer->getEdition()->setArchived(true);
        $this->em->flush();

        $this->cron()(Cron::SCOPE_CLI);

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

    private function cron(bool $canComplete = true): NotifyOfferComplete
    {
        $repository = $this->em->getRepository(Offer::class);
        \assert($repository instanceof OfferRepositoryInterface);

        $workflow = $this->createStub(WorkflowInterface::class);
        $workflow->method('can')->willReturn($canComplete);

        return new NotifyOfferComplete($repository, $this->bus, $workflow);
    }

    /**
     * Event over, not completed yet, in an open edition that runs a survey.
     */
    private function eligibleOffer(): OfferInterface
    {
        return $this->createOffer(
            status: [OfferStatus::PUBLISHED],
            dates: [[new \DateTimeImmutable('-3 days'), new \DateTimeImmutable('-2 days')]],
            edition: $this->createEdition(),
        );
    }

    private function log(string $message, OfferInterface $offer): void
    {
        $this->em->persist(new MessengerLog($message, [$offer]));
        $this->em->flush();
        $this->em->clear();
    }
}
