<?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\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
use Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
use Chill\TicketBundle\Entity\Motive;
use Chill\TicketBundle\Entity\MotiveHistory;
use Chill\TicketBundle\Entity\Ticket;
use Chill\TicketBundle\Event\MotiveUpdateEvent;
use Chill\TicketBundle\Event\TicketUpdateEvent;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * @internal
 *
 * @covers \Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler
 */
final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
{
    use ProphecyTrait;

    private function buildHandler(
        EntityManagerInterface $entityManager,
        ?ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler = null,
        ?EventDispatcherInterface $eventDispatcher = null,
    ): ReplaceMotiveCommandHandler {
        $clock = new MockClock();

        if (null === $changeEmergencyStateCommandHandler) {
            $changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class)->reveal();
        }
        if (null === $eventDispatcher) {
            $eventDispatcher = $this->prophesize(EventDispatcherInterface::class)->reveal();
        }

        return new ReplaceMotiveCommandHandler($clock, $entityManager, $changeEmergencyStateCommandHandler, $eventDispatcher);
    }

    public function testHandleOnTicketWithoutMotive(): void
    {
        $motive = new Motive();
        $ticket = new Ticket();

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
            if (!$arg instanceof MotiveHistory) {
                return false;
            }

            return $arg->getMotive() === $motive;
        }))->shouldBeCalled();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(fn ($event) => $event instanceof MotiveUpdateEvent
                && $event->newMotive === $motive
                && null === $event->previousMotive
                && $event->hasChanges()),
            TicketUpdateEvent::class
        )->shouldBeCalled();

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

        $handler->handle($ticket, new ReplaceMotiveCommand($motive));

        self::assertSame($motive, $ticket->getMotive());
    }

    public function testHandleReplaceMotiveOnTicketWithExistingMotive(): void
    {
        $motive = new Motive();
        $ticket = new Ticket();
        $history = new MotiveHistory(new Motive(), $ticket);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
            if (!$arg instanceof MotiveHistory) {
                return false;
            }

            return $arg->getMotive() === $motive;
        }))->shouldBeCalled();

        $previous = $history->getMotive();
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(fn ($event) => $event instanceof MotiveUpdateEvent
                && $event->newMotive === $motive
                && $previous === $event->previousMotive
                && $event->hasChanges()),
            TicketUpdateEvent::class
        )->shouldBeCalled();

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

        $handler->handle($ticket, new ReplaceMotiveCommand($motive));

        self::assertSame($motive, $ticket->getMotive());
        self::assertCount(2, $ticket->getMotiveHistories());
    }

    public function testHandleReplaceMotiveOnTicketWithSameMotive(): void
    {
        $motive = new Motive();
        $ticket = new Ticket();
        $history = new MotiveHistory($motive, $ticket);

        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
            if (!$arg instanceof MotiveHistory) {
                return false;
            }

            return $arg->getMotive() === $motive;
        }))->shouldNotBeCalled();

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(Argument::any(), TicketUpdateEvent::class)->shouldNotBeCalled();

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

        $handler->handle($ticket, new ReplaceMotiveCommand($motive));

        self::assertSame($motive, $ticket->getMotive());
        self::assertCount(1, $ticket->getMotiveHistories());
    }

    public function testHandleUpdatesEmergencyStatusWhenMotiveHasMakeTicketEmergency(): void
    {
        // Create a motive with makeTicketEmergency set to YES
        $motive = new Motive();
        $motive->setMakeTicketEmergency(EmergencyStatusEnum::YES);

        // Create a ticket with no emergency status
        $ticket = new Ticket();

        // Mock the entity manager
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();

        // Mock the ChangeEmergencyStateCommandHandler
        $changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
        $changeEmergencyStateCommandHandler->__invoke(
            $ticket,
            Argument::that(fn (ChangeEmergencyStateCommand $command) => EmergencyStatusEnum::YES === $command->newEmergencyStatus)
        )->shouldBeCalled();

        // Expect event dispatch for motive update
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(Argument::type(MotiveUpdateEvent::class), TicketUpdateEvent::class)->shouldBeCalled();

        // Create the handler with our mocks
        $handler = $this->buildHandler(
            $entityManager->reveal(),
            $changeEmergencyStateCommandHandler->reveal(),
            $eventDispatcher->reveal()
        );

        // Handle the command
        $handler->handle($ticket, new ReplaceMotiveCommand($motive));

        // Assert that the motive was set on the ticket
        self::assertSame($motive, $ticket->getMotive());
    }

    public function testHandleDoesNotUpdateEmergencyStatusWhenMotiveHasNoMakeTicketEmergency(): void
    {
        // Create a motive with makeTicketEmergency set to null
        $motive = new Motive();
        $motive->setMakeTicketEmergency(null);

        // Create a ticket with no emergency status
        $ticket = new Ticket();

        // Mock the entity manager
        $entityManager = $this->prophesize(EntityManagerInterface::class);
        $entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();

        // Mock the ChangeEmergencyStateCommandHandler - it should NOT be called
        $changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
        $changeEmergencyStateCommandHandler->__invoke(
            Argument::cetera()
        )->shouldNotBeCalled();

        // Expect event dispatch for motive update
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(Argument::type(MotiveUpdateEvent::class), TicketUpdateEvent::class)->shouldBeCalled();

        // Create the handler with our mocks
        $handler = $this->buildHandler(
            $entityManager->reveal(),
            $changeEmergencyStateCommandHandler->reveal(),
            $eventDispatcher->reveal()
        );

        // Handle the command
        $handler->handle($ticket, new ReplaceMotiveCommand($motive));

        // Assert that the motive was set on the ticket
        self::assertSame($motive, $ticket->getMotive());
    }
}
