<?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 Notification\Email;

use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\NotificationComment;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Email\NotificationMailer;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PostPersistEventArgs;
use PHPUnit\Framework\MockObject\Exception;
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\MessageBusInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Translation\Translator;
use Symfony\Contracts\Translation\TranslatorInterface;
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage;

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

    public function testPostPersistComment(): void
    {
        $user1 = (new User())->setEmail('user1@foo.com');
        $user2 = (new User())->setEmail('user2@foo.com');
        $user3 = (new User())->setEmail('user3@foo.com');

        $notification = new Notification();
        $notification
            ->setTitle('test notification')
            ->setSender($user1)
            ->addAddressee($user2)
            ->addAddressee($user3)
        ;

        $comment = (new NotificationComment())
            ->setContent('foo bar baz')
            ->setCreatedBy($user2)
        ;
        $notification->addComment($comment);

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

        // a mail only to user1 and user3 should have been sent
        $mailer->send(Argument::that(function (Email $email) {
            foreach ($email->getTo() as $address) {
                if ('user1@foo.com' === $address->getAddress() || 'user3@foo.com' === $address->getAddress()) {
                    return true;
                }
            }

            return false;
        }))->shouldBeCalledTimes(2);

        $objectManager = $this->prophesize(EntityManagerInterface::class);

        $mailer = $this->buildNotificationMailer($mailer->reveal());
        $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal()));
    }

    public function testPostPersistCommentDestWithNullEmail(): void
    {
        $user1 = (new User())->setEmail('user1@foo.com');
        $user2 = (new User())->setEmail('user2@foo.com');
        $user3 = (new User())->setEmail(null);

        $notification = new Notification();
        $notification
            ->setTitle('test notification')
            ->setSender($user1)
            ->addAddressee($user2)
            ->addAddressee($user3)
        ;

        $comment = (new NotificationComment())
            ->setContent('foo bar baz')
            ->setCreatedBy($user2)
        ;
        $notification->addComment($comment);

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

        // a mail only to user1 and user3 should have been sent
        $mailer->send(Argument::that(function (Email $email) {
            foreach ($email->getTo() as $address) {
                if ('user1@foo.com' === $address->getAddress()) {
                    return true;
                }
            }

            return false;
        }))->shouldBeCalledTimes(1);

        $objectManager = $this->prophesize(EntityManagerInterface::class);

        $mailer = $this->buildNotificationMailer($mailer->reveal());
        $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal()));
    }

    /**
     * @throws \ReflectionException
     * @throws Exception
     */
    public function testProcessNotificationForAddresseeWithImmediateEmailPreference(): void
    {
        // Create a real notification entity
        $notification = new Notification();
        $notification->setType('test_notification_type');

        // Use reflection to set the ID since it's normally generated by the database
        $reflectionNotification = new \ReflectionClass(Notification::class);
        $idProperty = $reflectionNotification->getProperty('id');
        $idProperty->setAccessible(true);
        $idProperty->setValue($notification, 123);

        // Create a real user entity
        $user = new User();
        $user->setEmail('user@example.com');

        // Use reflection to set the ID since it's normally generated by the database
        $reflectionUser = new \ReflectionClass(User::class);
        $idProperty = $reflectionUser->getProperty('id');
        $idProperty->setAccessible(true);
        $idProperty->setValue($user, 456);

        // Set notification flags for the user
        $user->setNotificationImmediately('test_notification_type', true);

        $messageBus = $this->createMock(MessageBusInterface::class);
        $messageBus->expects($this->once())
            ->method('dispatch')
            ->with($this->callback(fn (SendImmediateNotificationEmailMessage $message) => 123 === $message->getNotificationId()
                && 456 === $message->getAddresseeId()))
            ->willReturn(new Envelope(new \stdClass()));

        $mailer = $this->buildNotificationMailer(null, $messageBus);

        // Call the method that processes notifications
        $reflection = new \ReflectionClass(NotificationMailer::class);
        $method = $reflection->getMethod('processNotificationForAddressee');
        $method->setAccessible(true);
        $method->invoke($mailer, $notification, $user);
    }

    public function testSendDailyDigest(): void
    {
        // Create a user
        $user = new User();
        $user->setEmail('user@example.com');

        // Create some notifications
        $notification = $this->prophesize(Notification::class);
        $notification->getTitle()->willReturn('Notification 1');
        $notification->getMessage()->willReturn('Message 1');
        $notification->getId()->willReturn(123);

        $notification2 = $this->prophesize(Notification::class);
        $notification2->getTitle()->willReturn('Notification 2');
        $notification2->getMessage()->willReturn('Message 2');
        $notification2->getId()->willReturn(456);

        $notifications = [$notification, $notification2];

        // Mock the mailer to verify that an email is sent with the correct parameters
        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send(Argument::that(function ($email) use ($user) {
            // Verify that the email is sent to the correct user
            foreach ($email->getTo() as $address) {
                if ($user->getEmail() === $address->getAddress()) {
                    return true;
                }
            }

            return false;
        }))->shouldBeCalledOnce();

        // Create a translator that returns a fixed string for the subject
        $translator = $this->prophesize(TranslatorInterface::class);
        $translator->trans('notification.Daily Notification Digest')->willReturn('Daily Digest');

        // Create the notification mailer with the mocked mailer and translator
        $notificationMailer = $this->buildNotificationMailer($mailer->reveal(), null, $translator->reveal());

        // Call the sendDailyDigest method
        $notificationMailer->sendDailyDigest($user, $notifications);
    }

    public function testSendDailyDigestWithNoNotifications(): void
    {
        // Create a user
        $user = new User();
        $user->setEmail('user@example.com');

        // Empty notifications array
        $notifications = [];

        // Mock the mailer to verify that no email is sent
        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send(Argument::any())->shouldNotBeCalled();

        // Create the notification mailer with the mocked mailer
        $notificationMailer = $this->buildNotificationMailer($mailer->reveal());

        // Call the sendDailyDigest method
        $notificationMailer->sendDailyDigest($user, $notifications);
    }

    public function testSendDailyDigestWithUserHavingNoEmail(): void
    {
        // Create a user with no email
        $user = new User();
        $user->setEmail(null);

        // Create some notifications
        $notification = $this->prophesize(Notification::class);
        $notification->getTitle()->willReturn('Notification 1');
        $notification->getMessage()->willReturn('Message 1');
        $notification->getId()->willReturn(123);

        $notifications = [$notification];

        // Mock the mailer to verify that no email is sent
        $mailer = $this->prophesize(MailerInterface::class);
        $mailer->send(Argument::any())->shouldNotBeCalled();

        // Create the notification mailer with the mocked mailer
        $notificationMailer = $this->buildNotificationMailer($mailer->reveal());

        // Call the sendDailyDigest method
        $notificationMailer->sendDailyDigest($user, $notifications);
    }

    private function buildNotificationMailer(
        ?MailerInterface $mailer = null,
        ?MessageBusInterface $messageBus = null,
        ?TranslatorInterface $translator = null,
    ): NotificationMailer {
        return new NotificationMailer(
            $mailer ?? $this->prophesize(MailerInterface::class)->reveal(),
            new NullLogger(),
            $messageBus ?? $this->prophesize(MessageBusInterface::class)->reveal(),
            $translator ?? new Translator('fr')
        );
    }
}
