<?php

namespace App\Tests\Unit\Service;

use App\Entity\Access\Access;
use App\Entity\Core\File;
use App\Entity\Core\Notification;
use App\Entity\Organization\Organization;
use App\Enum\Core\NotificationTypeEnum;
use App\Service\MercureHub;
use App\Service\Notifier;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;

class TestableNotifier extends Notifier {
    public function createNotification(Access $access, string $name, string $type, array $message, string $link = null): Notification {
        return parent::createNotification($access, $name, $type, $message, $link);
    }
}

class NotifierTest extends TestCase
{
    private EntityManagerInterface $em;
    private MercureHub $mercureHub;

    public function setUp(): void {
        $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
        $this->mercureHub = $this->getMockBuilder(MercureHub::class)->disableOriginalConstructor()->getMock();
    }

    /**
     * @see Notifier::createNotification()
     */
    public function testCreateNotification(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['createNotification'])
            ->getMock();

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $access = $this->getMockBuilder(Access::class)->getMock();
        $access->method('getId')->willReturn(1);
        $access->method('getOrganization')->willReturn($organization);

        $notification = $notifier->createNotification(
            $access,
            'test',
            NotificationTypeEnum::MESSAGE()->getValue(),
            ['message'],
            'link'
        );

        $this->assertEquals('test', $notification->getName());
        $this->assertEquals($access, $notification->getRecipientAccess());
        $this->assertEquals($organization, $notification->getRecipientOrganization());
        $this->assertEquals(NotificationTypeEnum::MESSAGE()->getValue(), $notification->getType());
        $this->assertEquals(['message'], $notification->getMessage());
        $this->assertEquals('link', $notification->getLink());
        $this->assertInstanceOf(\DateTime::class, $notification->getCreateDate());
        $this->assertInstanceOf(\DateTime::class, $notification->getUpdateDate());
    }

    /**
     * @see Notifier::notify()
     */
    public function testNotify(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['notify'])
            ->getMock();

        $notification = $this->getMockBuilder(Notification::class)->getMock();
        $access = $this->getMockBuilder(Access::class)->getMock();
        $access->method('getId')->willReturn(1);

        $notifier
            ->expects(self::once())
            ->method('createNotification')
            ->with($access, 'test', NotificationTypeEnum::MESSAGE()->getValue(), ['message'], null)
            ->willReturn($notification);

        $this->em->expects(self::once())->method('persist')->with($notification);
        $this->em->expects(self::once())->method('flush');
        $this->mercureHub->expects(self::once())->method('publishCreate')->with(1, $notification);

        $returned = $notifier->notify($access, 'test', NotificationTypeEnum::MESSAGE()->getValue(), ['message']);

        $this->assertEquals($notification, $returned);
    }

    /**
     * @see Notifier::notifyExport()
     */
    public function testNotifyExport(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['notifyExport'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();
        $file = $this->getMockBuilder(File::class)->getMock();
        $file->method('getId')->willReturn(1);
        $file->method('getName')->willReturn('foo.txt');

        $notifier
            ->expects(self::once())
            ->method('notify')
            ->with($access, 'export', NotificationTypeEnum::FILE()->getValue(), ['fileName' => 'foo.txt'], '/api/files/1/download');

        $notifier->notifyExport($access, $file);
    }

    /**
     * @see Notifier::notifyMessage()
     */
    public function testNotifyMessage(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['notifyMessage'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $notifier
            ->expects(self::once())
            ->method('notify')
            ->with($access, 'message', NotificationTypeEnum::MESSAGE()->getValue(), ['a message']);

        $notifier->notifyMessage($access, ['a message']);
    }

    /**
     * @see Notifier::notifySystem()
     */
    public function testNotifySystem(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['notifySystem'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $notifier
            ->expects(self::once())
            ->method('notify')
            ->with($access, 'message', NotificationTypeEnum::SYSTEM()->getValue(), ['a message']);

        $notifier->notifySystem($access, ['a message']);
    }

    /**
     * @see Notifier::notifyError()
     */
    public function testNotifyError(): void {
        $notifier = $this
            ->getMockBuilder(TestableNotifier::class)
            ->setConstructorArgs([$this->em, $this->mercureHub])
            ->setMethodsExcept(['notifyError'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $notifier
            ->expects(self::once())
            ->method('notify')
            ->with($access, 'test', NotificationTypeEnum::ERROR()->getValue(), ['a message']);

        $notifier->notifyError($access, 'test', ['a message']);
    }
}
