<?php

namespace App\Tests\Unit\Service;

use ApiPlatform\Api\IriConverterInterface;
use App\Entity\Organization\Organization;
use App\Service\MercureHub;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\SerializerInterface;

class TestableMercureHub extends MercureHub {
    public function createUpdate($accessId, string $data): Update { return parent::createUpdate($accessId, $data); }
}

class MercureHubTest extends TestCase
{
    private HubInterface $hub;
    private SerializerInterface $serializer;
    private EncoderInterface $encoder;
    private IriConverterInterface $iriConverter;

    public function setUp(): void {
        $this->hub = $this->getMockBuilder(HubInterface::class)->disableOriginalConstructor()->getMock();
        $this->serializer = $this->getMockBuilder(SerializerInterface::class)->disableOriginalConstructor()->getMock();
        $this->encoder = $this->getMockBuilder(EncoderInterface::class)->disableOriginalConstructor()->getMock();
        $this->iriConverter = $this->getMockBuilder(IriConverterInterface::class)->disableOriginalConstructor()->getMock();
    }

    /**
     * @see MercureHub::createUpdate()
     */
    public function testCreateUpdate(): void {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['createUpdate'])
            ->getMock();

        $update = $mercureHub->createUpdate(1, "{'foo': 1}");

        $this->assertEquals(['access/1'], $update->getTopics());
        $this->assertEquals("{'foo': 1}", $update->getData());
        $this->assertEquals(true, $update->isPrivate());
    }

    /**
     * @see MercureHub::publish()
     */
    public function testPublish(): void
    {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['publish'])
            ->getMock();

        $entity = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $this->iriConverter->expects(self::once())->method('getIriFromResource')->with($entity)->willReturn('/api/entity/1');
        $this->serializer->expects(self::once())->method('serialize')->with($entity, 'jsonld', [])->willReturn("{'foo': 1}");

        $this->encoder
            ->expects(self::once())
            ->method('encode')
            ->with([
                'iri' => '/api/entity/1',
                'operation' => 'update',
                'data' => "{'foo': 1}"
            ], 'jsonld')
            ->willReturn("{'iri':'/api/entity/1','operation':'update','data':'{\'foo\':1}'");

        $update = new Update(
            'access/1',
            "{'iri':'/api/entity/1','operation':'update','data':'{\'foo\':1}'",
            true
        );

        $mercureHub
            ->expects(self::once())
            ->method('createUpdate')
            ->with(1, "{'iri':'/api/entity/1','operation':'update','data':'{\'foo\':1}'")
            ->willReturn($update);

        $this->hub->expects(self::once())->method('publish')->with($update);

        $mercureHub->publish(1, $entity);
    }

    /**
     * @see MercureHub::publish()
     */
    public function testPublishInvalidOperation(): void
    {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['publish'])
            ->getMock();

        $entity = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $this->expectException(\InvalidArgumentException::class);
        $mercureHub->publish(1, $entity, 'foo');
    }

    /**
     * @see MercureHub::publishUpdate()
     */
    public function testPublishUpdate(): void {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['publishUpdate'])
            ->getMock();

        $entity = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $mercureHub->expects(self::once())->method('publish')->with(1, $entity, 'update');
        $mercureHub->publishUpdate(1, $entity);
    }

    /**
     * @see MercureHub::publishCreate()
     */
    public function testPublishCreate(): void {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['publishCreate'])
            ->getMock();

        $entity = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $mercureHub->expects(self::once())->method('publish')->with(1, $entity, 'create');

        $mercureHub->publishCreate(1, $entity);
    }

    /**
     * @see MercureHub::publishDelete()
     */
    public function testPublishDelete(): void {
        $mercureHub = $this
            ->getMockBuilder(TestableMercureHub::class)
            ->setConstructorArgs([$this->hub, $this->serializer, $this->encoder, $this->iriConverter])
            ->setMethodsExcept(['publishDelete'])
            ->getMock();

        $entity = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $mercureHub->expects(self::once())->method('publish')->with(1, $entity, 'delete');

        $mercureHub->publishDelete(1, $entity);
    }
}
