<?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\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Chill\TicketBundle\Action\Ticket\Handler\SetAddresseesCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetAddresseesCommand;
use Chill\TicketBundle\Controller\SetAddresseesController;
use Chill\TicketBundle\Entity\AddresseeHistory;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class SetAddresseesControllerTest extends KernelTestCase
{
    use ProphecyTrait;

    private SerializerInterface $serializer;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->serializer = self::getContainer()->get(SerializerInterface::class);
    }

    /**
     * @dataProvider getContentData
     */
    public function testSetAddresseesWithValidData(array $bodyAsArray): void
    {
        $controller = $this->buildController(true, true);
        $request = new Request(content: json_encode(['addressees' => $bodyAsArray], JSON_THROW_ON_ERROR, 512));
        $ticket = new Ticket();

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

        self::assertEquals(200, $response->getStatusCode());

        $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
        self::assertIsArray($asArray);
        self::assertArrayHasKey('type', $asArray);
        self::assertEquals('ticket_ticket', $asArray['type']);
    }

    /**
     * @dataProvider getContentData
     */
    public function testSetAddresseesWithInvalidData(array $bodyAsArray): void
    {
        $controller = $this->buildController(false, false);
        $request = new Request(content: json_encode(['addressees' => $bodyAsArray], JSON_THROW_ON_ERROR, 512));
        $ticket = new Ticket();

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

        self::assertEquals(422, $response->getStatusCode());

        $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
        self::assertIsArray($asArray);
        self::arrayHasKey('violations');
        self::assertGreaterThan(0, count($asArray['violations']));
    }

    public static function getContentData(): iterable
    {
        self::bootKernel();
        $entityManager = self::getContainer()->get(EntityManagerInterface::class);

        $userGroup = $entityManager->createQuery('SELECT ug FROM '.UserGroup::class.' ug ')
            ->setMaxResults(1)->getOneOrNullResult();

        if (null === $userGroup) {
            throw new \RuntimeException('User group not existing in database');
        }

        $user = $entityManager->createQuery('SELECT u FROM '.User::class.' u')
            ->setMaxResults(1)->getOneOrNullResult();

        if (null === $user) {
            throw new \RuntimeException('User not existing in database');
        }

        self::ensureKernelShutdown();

        yield [[['type' => 'user', 'id' => $user->getId()]]];
        yield [[['type' => 'user', 'id' => $user->getId()], ['type' => 'user_group', 'id' => $userGroup->getId()]]];
        yield [[['type' => 'user_group', 'id' => $userGroup->getId()]]];
    }

    /**
     * @dataProvider getContentDataUnique
     */
    public function testAddAddresseeWithValidData(array $bodyAsArray): void
    {
        $controller = $this->buildController(true, true);
        $request = new Request(content: json_encode(['addressee' => $bodyAsArray], JSON_THROW_ON_ERROR, 512));
        $ticket = new Ticket();

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

        self::assertEquals(200, $response->getStatusCode());

        $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
        self::assertIsArray($asArray);
        self::assertArrayHasKey('type', $asArray);
        self::assertEquals('ticket_ticket', $asArray['type']);
    }

    /**
     * @throws \JsonException
     *
     * @dataProvider getContentDataUnique
     */
    public function testAddAddresseeWithInvalidData(array $bodyAsArray): void
    {
        $controller = $this->buildController(false, false);
        $request = new Request(content: json_encode(['addressee' => $bodyAsArray], JSON_THROW_ON_ERROR, 512));
        $ticket = new Ticket();

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

        self::assertEquals(422, $response->getStatusCode());

        $asArray = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
        self::assertIsArray($asArray);
        self::arrayHasKey('violations');
        self::assertGreaterThan(0, count($asArray['violations']));
    }

    public static function getContentDataUnique(): iterable
    {
        $entityManager = self::getContainer()->get(EntityManagerInterface::class);

        $userGroup = $entityManager->createQuery('SELECT ug FROM '.UserGroup::class.' ug ')
            ->setMaxResults(1)->getOneOrNullResult();

        if (null === $userGroup) {
            throw new \RuntimeException('User group not existing in database');
        }

        $user = $entityManager->createQuery('SELECT u FROM '.User::class.' u')
            ->setMaxResults(1)->getOneOrNullResult();

        if (null === $user) {
            throw new \RuntimeException('User not existing in database');
        }

        self::ensureKernelShutdown();

        yield [['type' => 'user', 'id' => $user->getId()]];
        yield [['type' => 'user_group', 'id' => $userGroup->getId()]];
    }

    private function buildController(bool $willSave, bool $isValid): SetAddresseesController
    {
        $user = new User();
        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true);
        $security->getUser()->willReturn($user);

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

        if ($willSave) {
            $entityManager->flush()->shouldBeCalled();
            $entityManager->persist(Argument::type(AddresseeHistory::class))->shouldBeCalled();
        }

        $validator = $this->prophesize(ValidatorInterface::class);

        if ($isValid) {
            $validator->validate(Argument::type(SetAddresseesCommand::class))->willReturn(new ConstraintViolationList([]));
        } else {
            $validator->validate(Argument::type(SetAddresseesCommand::class))->willReturn(
                new ConstraintViolationList([
                    new ConstraintViolation('Fake constraint', 'fake message template', [], [], 'addresses', []),
                ])
            );
        }

        return new SetAddresseesController(
            $security->reveal(),
            $entityManager->reveal(),
            $this->serializer,
            new SetAddresseesCommandHandler(new MockClock(), $entityManager->reveal(), $security->reveal()),
            $validator->reveal()
        );
    }
}
