<?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\Action\Ticket\Handler;

use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\Person;
use Chill\TicketBundle\Action\Ticket\Handler\SetPersonsCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetPersonsCommand;
use Chill\TicketBundle\Entity\PersonHistory;
use Chill\TicketBundle\Entity\Ticket;
use Chill\TicketBundle\Event\PersonsUpdateEvent;
use Chill\TicketBundle\Event\TicketUpdateEvent;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

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

    public function testHandleOnEmptyAddresses(): void
    {
        $ticket = new Ticket();
        $command = new SetPersonsCommand([$person1 = new Person(), $group1 = new Person()]);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person1))->shouldBeCalledOnce();
        $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $group1))->shouldBeCalledOnce();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(fn ($arg) => $arg instanceof PersonsUpdateEvent
                && in_array($person1, $arg->personsAdded, true)
                && in_array($group1, $arg->personsAdded, true)
                && [] === $arg->personsRemoved),
            TicketUpdateEvent::class
        )->shouldBeCalled();

        $handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());

        $handler->handle($ticket, $command);

        self::assertCount(2, $ticket->getPersons());
    }

    public function testHandleExistingPersonIsNotRemovedNorCreatingDouble(): void
    {
        $ticket = new Ticket();
        $person = new Person();
        $history = new PersonHistory($person, $ticket, new \DateTimeImmutable('1 month ago'));
        $command = new SetPersonsCommand([$person]);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person))->shouldNotBeCalled();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(
                fn ($arg) => $arg instanceof PersonsUpdateEvent
            ),
            TicketUpdateEvent::class
        )->shouldNotBeCalled();

        $handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());

        $handler->handle($ticket, $command);

        self::assertNull($history->getEndDate());
        self::assertCount(1, $ticket->getPersons());
    }

    public function testHandleRemoveExistingPerson(): void
    {
        $ticket = new Ticket();
        $person = new Person();
        $person2 = new Person();
        $history = new PersonHistory($person, $ticket, new \DateTimeImmutable('1 month ago'));
        $command = new SetPersonsCommand([$person2]);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person2))->shouldBeCalled();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(
                fn ($arg) => $arg instanceof PersonsUpdateEvent
                && in_array($person, $arg->personsRemoved, true) && 1 === count($arg->personsRemoved)
                && in_array($person2, $arg->personsAdded, true) && 1 === count($arg->personsAdded)
            ),
            TicketUpdateEvent::class
        )->shouldBeCalled();

        $handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());

        $handler->handle($ticket, $command);

        self::assertNotNull($history->getEndDate());
        self::assertContains($person2, $ticket->getPersons());
    }

    public function testAddingDoublingPersonsDoesNotCreateDoubleHistories(): void
    {
        $ticket = new Ticket();
        $person = new Person();
        $command = new SetPersonsCommand([$person, $person]);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person))->shouldBeCalledOnce();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(
                fn ($arg) => $arg instanceof PersonsUpdateEvent
                && in_array($person, $arg->personsAdded, true) && 1 === count($arg->personsAdded)
                && [] === $arg->personsRemoved
            ),
            TicketUpdateEvent::class
        )->shouldBeCalled();

        $handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());

        $handler->handle($ticket, $command);

        self::assertCount(1, $ticket->getPersons());
    }

    private function buildHandler(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher): SetPersonsCommandHandler
    {
        $security = $this->prophesize(Security::class);
        $security->getUser()->willReturn(new User());

        return new SetPersonsCommandHandler(new MockClock(), $entityManager, $security->reveal(), $eventDispatcher);
    }
}
