<?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\TicketBundle\Tests\Controller;

use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
use Chill\TicketBundle\Controller\SetCallerApiController;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;

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

    public function testSetCallerWithoutPermission(): void
    {
        $ticket = new Ticket();
        $request = new Request();

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(false);

        $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $serializer = $this->prophesize(SerializerInterface::class);

        $controller = new SetCallerApiController(
            $setCallerCommandHandler->reveal(),
            $security->reveal(),
            $entityManager->reveal(),
            $serializer->reveal(),
        );

        $this->expectException(AccessDeniedHttpException::class);
        $controller->setCaller($ticket, $request);
    }

    public function testSetCallerWithInvalidRequestBody(): void
    {
        $ticket = new Ticket();
        $request = new Request([], [], [], [], [], [], 'invalid json');

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true);

        $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
        $entityManager = $this->prophesize(EntityManagerInterface::class);

        $serializer = $this->prophesize(SerializerInterface::class);
        $serializer->deserialize('invalid json', SetCallerCommand::class, 'json')
            ->willThrow(new \Exception('Invalid JSON'));

        $controller = new SetCallerApiController(
            $setCallerCommandHandler->reveal(),
            $security->reveal(),
            $entityManager->reveal(),
            $serializer->reveal(),
        );

        $this->expectException(BadRequestHttpException::class);
        $controller->setCaller($ticket, $request);
    }

    public function testSetCallerWithValidRequest(): void
    {
        $ticket = new Ticket();
        $request = new Request([], [], [], [], [], [], '{"caller": {"id": 123, "type": "person"}}');

        $person = new Person();
        $command = new SetCallerCommand($person);

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true);

        $serializer = $this->prophesize(SerializerInterface::class);
        $serializer->deserialize('{"caller": {"id": 123, "type": "person"}}', SetCallerCommand::class, 'json')
            ->willReturn($command);
        $serializer->serialize($ticket, 'json', ['groups' => ['read']])
            ->willReturn('{}')
            ->shouldBeCalled();

        $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
        $setCallerCommandHandler->__invoke($ticket, $command)
            ->willReturn($ticket)
            ->shouldBeCalled();

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

        $controller = new SetCallerApiController(
            $setCallerCommandHandler->reveal(),
            $security->reveal(),
            $entityManager->reveal(),
            $serializer->reveal(),
        );

        $response = $controller->setCaller($ticket, $request);

        $this->assertInstanceOf(JsonResponse::class, $response);
    }

    public function testSetCallerWithThirdParty(): void
    {
        $ticket = new Ticket();
        $request = new Request([], [], [], [], [], [], '{"caller": {"id": 456, "type": "thirdParty"}}');

        $thirdParty = $this->prophesize(ThirdParty::class)->reveal();
        $command = new SetCallerCommand($thirdParty);

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true);

        $serializer = $this->prophesize(SerializerInterface::class);
        $serializer->deserialize('{"caller": {"id": 456, "type": "thirdParty"}}', SetCallerCommand::class, 'json')
            ->willReturn($command);
        $serializer->serialize($ticket, 'json', ['groups' => ['read']])
            ->willReturn('{}')
            ->shouldBeCalled();

        $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
        $setCallerCommandHandler->__invoke($ticket, $command)
            ->willReturn($ticket)
            ->shouldBeCalled();

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

        $controller = new SetCallerApiController(
            $setCallerCommandHandler->reveal(),
            $security->reveal(),
            $entityManager->reveal(),
            $serializer->reveal(),
        );

        $response = $controller->setCaller($ticket, $request);

        $this->assertInstanceOf(JsonResponse::class, $response);
    }

    public function testSetCallerToNull(): void
    {
        $ticket = new Ticket();
        $request = new Request([], [], [], [], [], [], '{"caller": null}');

        $command = new SetCallerCommand(null);

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true);

        $serializer = $this->prophesize(SerializerInterface::class);
        $serializer->deserialize('{"caller": null}', SetCallerCommand::class, 'json')
            ->willReturn($command);
        $serializer->serialize($ticket, 'json', ['groups' => ['read']])
            ->willReturn('{}')
            ->shouldBeCalled();

        $setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
        $setCallerCommandHandler->__invoke($ticket, $command)
            ->willReturn($ticket)
            ->shouldBeCalled();

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

        $controller = new SetCallerApiController(
            $setCallerCommandHandler->reveal(),
            $security->reveal(),
            $entityManager->reveal(),
            $serializer->reveal(),
        );

        $response = $controller->setCaller($ticket, $request);

        $this->assertInstanceOf(JsonResponse::class, $response);
    }
}
