<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\DocGeneratorBundle\tests\Service\Messenger;

use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
use Chill\DocGeneratorBundle\Service\Messenger\OnGenerationFails;
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\RawMessage;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class OnGenerationFailsTest extends TestCase
{
    use ProphecyTrait;

    public function testNotConcernedMessageAreNotHandled(): void
    {
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldNotBeCalled();

        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send()->shouldNotBeCalled();

        $eventSubscriber = $this->buildOnGenerationFailsEventSubscriber(
            entityManager: $entityManager->reveal(),
            mailer: $mailer->reveal()
        );

        $event = $this->buildEvent(new \stdClass());

        $eventSubscriber->onMessageFailed($event);
    }

    public function testMessageThatWillBeRetriedAreNotHandled(): void
    {
        $storedObject = new StoredObject();

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldNotBeCalled();

        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send()->shouldNotBeCalled();

        $eventSubscriber = $this->buildOnGenerationFailsEventSubscriber(
            entityManager: $entityManager->reveal(),
            mailer: $mailer->reveal()
        );

        $event = $this->buildEvent($this->buildRequestGenerationMessage($storedObject));
        $event->setForRetry();

        $eventSubscriber->onMessageFailed($event);
    }

    public function testThatANotRetriyableEventWillMarkObjectAsFailed(): void
    {
        $storedObject = new StoredObject();

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldBeCalled();

        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send(Argument::type(RawMessage::class), Argument::any())->shouldBeCalled();

        $eventSubscriber = $this->buildOnGenerationFailsEventSubscriber(
            entityManager: $entityManager->reveal(),
            mailer: $mailer->reveal(),
            storedObject: $storedObject
        );

        $event = $this->buildEvent($this->buildRequestGenerationMessage($storedObject));

        $eventSubscriber->onMessageFailed($event);

        self::assertEquals(StoredObject::STATUS_FAILURE, $storedObject->getStatus());
    }

    public function testThatANonRetryableEventSendAnEmail(): void
    {
        $storedObject = new StoredObject();

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->flush()->shouldBeCalled();

        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send(
            Argument::that(function ($arg): bool {
                if (!$arg instanceof Email) {
                    return false;
                }

                foreach ($arg->getTo() as $to) {
                    if ('test@test.com' === $to->getAddress()) {
                        return true;
                    }
                }

                return false;
            }),
            Argument::any()
        )
            ->shouldBeCalled();

        $eventSubscriber = $this->buildOnGenerationFailsEventSubscriber(
            entityManager: $entityManager->reveal(),
            mailer: $mailer->reveal(),
            storedObject: $storedObject
        );

        $event = $this->buildEvent($this->buildRequestGenerationMessage($storedObject, sendResultToEmail: 'test@test.com'));

        $eventSubscriber->onMessageFailed($event);
    }

    private function buildRequestGenerationMessage(
        StoredObject $destinationStoredObject,
        ?User $creator = null,
        ?DocGeneratorTemplate $template = null,
        array $contextGenerationData = [],
        bool $isTest = false,
        ?string $sendResultToEmail = null,
    ): RequestGenerationMessage {
        if (null === $creator) {
            $creator = new User();
            $creator->setEmail('fake@example.com');
        }

        if (null === $creator->getId()) {
            $class = new \ReflectionClass($creator);
            $property = $class->getProperty('id');
            $property->setAccessible(true);
            $property->setValue($creator, 1);
        }

        $template ??= new DocGeneratorTemplate();
        $class = new \ReflectionClass($template);
        $property = $class->getProperty('id');
        $property->setAccessible(true);
        $property->setValue($template, 2);

        $class = new \ReflectionClass($destinationStoredObject);
        $property = $class->getProperty('id');
        $property->setAccessible(true);
        $property->setValue($destinationStoredObject, 3);

        return new RequestGenerationMessage(
            $creator,
            $template,
            1,
            $destinationStoredObject,
            $contextGenerationData,
            $isTest,
            $sendResultToEmail
        );
    }

    private function buildOnGenerationFailsEventSubscriber(
        ?StoredObject $storedObject = null,
        ?EntityManagerInterface $entityManager = null,
        ?MailerInterface $mailer = null,
    ): OnGenerationFails {
        $storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
        $storedObjectRepository->find(Argument::type('int'))->willReturn($storedObject ?? new StoredObject());

        if (null === $entityManager) {
            $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class);
        }

        if (null === $mailer) {
            $mailerProphecy = $this->prophesize(MailerInterface::class);
        }

        $translator = $this->prophesize(TranslatorInterface::class);
        $translator->trans(Argument::type('string'))->will(fn ($args) => $args[0]);

        $userRepository = $this->prophesize(UserRepositoryInterface::class);
        $userRepository->find(Argument::type('int'))->willReturn(new User());

        $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
        $docGeneratorTemplateRepository->find(Argument::type('int'))->willReturn(new DocGeneratorTemplate());

        return new OnGenerationFails(
            $docGeneratorTemplateRepository->reveal(),
            $entityManager ?? $entityManagerProphecy->reveal(),
            new NullLogger(),
            $mailer ?? $mailerProphecy->reveal(),
            $storedObjectRepository->reveal(),
            $translator->reveal(),
            $userRepository->reveal()
        );
    }

    private function buildEvent(object $message): WorkerMessageFailedEvent
    {
        $envelope = new Envelope($message);

        return new WorkerMessageFailedEvent($envelope, 'testing', new \RuntimeException());
    }
}
