<?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\MainBundle\Tests\Entity;

use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * @internal
 *
 * @coversNothing
 */
final class NotificationTest extends KernelTestCase
{
    private array $toDelete = [];

    protected function setUp(): void
    {
        self::bootKernel();
    }

    protected function tearDown(): void
    {
        $em = self::getContainer()->get(EntityManagerInterface::class);

        foreach ($this->toDelete as [$className, $id]) {
            $object = $em->find($className, $id);
            $em->remove($object);
        }

        $em->flush();
    }

    public function testAddAddresseeStoreAnUread()
    {
        $notification = new Notification();
        $notification->addAddressee($user1 = new User());
        $notification->addAddressee($user2 = new User());
        $notification->addAddressee($user3 = new User());
        $notification->addAddressee($user4 = new User());

        $this->assertCount(4, $notification->getAddressees());

        // launch listener
        $notification->registerUnread();
        $this->assertCount(4, $notification->getUnreadBy());
        $this->assertContains($user1, $notification->getUnreadBy()->toArray());
        $this->assertContains($user2, $notification->getUnreadBy()->toArray());
        $this->assertContains($user3, $notification->getUnreadBy()->toArray());

        $notification->markAsReadBy($user1);

        $this->assertCount(3, $notification->getUnreadBy());
        $this->assertNotContains($user1, $notification->getUnreadBy()->toArray());
    }

    public function testAddressesEmail(): void
    {
        $notification = new Notification();

        $notification->addAddressesEmail('test');
        $notification->addAddressesEmail('other');

        $this->assertContains('test', $notification->getAddressesEmails());
        $this->assertContains('other', $notification->getAddressesEmails());
        $this->assertContains('test', $notification->getAddressesEmailsAdded());
        $this->assertContains('other', $notification->getAddressesEmailsAdded());

        $notification->removeAddressesEmail('other');

        $this->assertNotContains('other', $notification->getAddressesEmails());
        $this->assertNotContains('other', $notification->getAddressesEmailsAdded());
    }

    public function testIsSendImmediately(): void
    {
        $notification = new Notification();
        $notification->setType('test_notification_type');

        $user = new User();

        // no notification flags
        $this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when no notification flags are set, by default immediate email');

        // immediate-email preference
        $user->setNotificationImmediately('test_notification_type', true);
        $user->setNotificationDailyDigest('test_notification_type', true);
        $this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when preferences contain immediate-email');

        // daily-email preference
        $user->setNotificationDailyDigest('test_notification_type', true);
        $user->setNotificationImmediately('test_notification_type', false);
        $this->assertFalse($user->isNotificationSendImmediately($notification->getType()), 'Should return false when preference is daily-email only');
        $this->assertTrue($user->isNotificationDailyDigest($notification->getType()), 'Should return true when preference is daily-email');

        // a different notification type
        $notification->setType('other_notification_type');
        $this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return false when notification type does not match any preference');
    }

    /**
     * @dataProvider generateNotificationData
     */
    public function testPrePersistComputeUnread(int $senderId, array $addressesIds)
    {
        $em = self::getContainer()->get(EntityManagerInterface::class);
        $notification = new Notification();
        $notification
            ->setSender($em->find(User::class, $senderId))
            ->setRelatedEntityId(0)
            ->setRelatedEntityClass(AccompanyingPeriod::class)
            ->setUpdatedAt(new \DateTimeImmutable())
            ->setMessage('Fake message');

        foreach ($addressesIds as $addresseeId) {
            $notification
                ->getAddressees()->add($em->find(User::class, $addresseeId));
        }

        $em->persist($notification);
        $em->flush();
        $em->refresh($notification);

        $this->toDelete[] = [Notification::class, $notification->getId()];

        $this->assertEquals($senderId, $notification->getSender()->getId());
        $this->assertCount(\count($addressesIds), $notification->getUnreadBy());

        $unreadIds = $notification->getUnreadBy()->map(static fn (User $u) => $u->getId());

        foreach ($addressesIds as $addresseeId) {
            $this->assertContains($addresseeId, $unreadIds);
        }
    }

    public static function generateNotificationData()
    {
        self::bootKernel();
        $userRepository = self::getContainer()->get(UserRepository::class);

        $senderId = $userRepository
            ->findOneBy(['username' => 'center b_social'])
            ->getId();

        $addressesIds = [];
        $addressesIds[] = $userRepository
            ->findOneBy(['username' => 'center b_direction'])
            ->getId();

        yield [
            $senderId,
            $addressesIds,
        ];
    }
}
