<?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\Entity\EmergencyStatusEnum;
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
use Chill\TicketBundle\Entity\Ticket;
use Chill\TicketBundle\Event\EmergencyStatusUpdateEvent;
use Chill\TicketBundle\Event\TicketUpdateEvent;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

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

    public function testInvokeWithAlreadyYesEmergencyStatus(): void
    {
        $ticket = new Ticket();

        // Create a YES emergency status history
        new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);

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

        $handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
        $command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);

        $result = $handler->__invoke($ticket, $command);

        // Assert that the ticket is returned unchanged
        $this->assertSame($ticket, $result);
        $this->assertSame(EmergencyStatusEnum::YES, $ticket->getEmergencyStatus());

        // Assert that no new emergency status history was created
        $emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
        $this->assertCount(1, $emergencyStatusHistories);
    }

    public function testInvokeWithYesEmergencyStatusToNo(): void
    {
        $ticket = new Ticket();

        // Create a YES emergency status history
        new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(
                fn ($e) => $e instanceof EmergencyStatusUpdateEvent
                && EmergencyStatusEnum::YES === $e->previousEmergencyStatus
                && EmergencyStatusEnum::NO === $e->newEmergencyStatus
            ),
            TicketUpdateEvent::class
        )->shouldBeCalled();

        $handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
        $command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::NO);

        $result = $handler->__invoke($ticket, $command);

        // Assert that the ticket is returned
        $this->assertSame($ticket, $result);

        // Assert that the ticket emergency status is now NO
        $this->assertSame(EmergencyStatusEnum::NO, $ticket->getEmergencyStatus());

        // Assert that the old emergency status history was ended and a new one was created
        $emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
        $this->assertCount(2, $emergencyStatusHistories);

        // The first emergency status history should be ended
        $yesEmergencyStatusHistory = $emergencyStatusHistories->first();
        $this->assertNotNull($yesEmergencyStatusHistory->getEndDate());
        $this->assertSame(EmergencyStatusEnum::YES, $yesEmergencyStatusHistory->getEmergencyStatus());

        // The last emergency status history should be NO and active
        $noEmergencyStatusHistory = $emergencyStatusHistories->last();
        $this->assertNull($noEmergencyStatusHistory->getEndDate());
        $this->assertSame(EmergencyStatusEnum::NO, $noEmergencyStatusHistory->getEmergencyStatus());
    }

    public function testInvokeWithNoEmergencyStatusToYes(): void
    {
        $ticket = new Ticket();

        // Create a NO emergency status history
        new EmergencyStatusHistory(EmergencyStatusEnum::NO, $ticket);

        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
        $eventDispatcher->dispatch(
            Argument::that(
                fn ($e) => $e instanceof EmergencyStatusUpdateEvent
                && EmergencyStatusEnum::NO === $e->previousEmergencyStatus
                && EmergencyStatusEnum::YES === $e->newEmergencyStatus
            ),
            TicketUpdateEvent::class
        )->shouldBeCalled();

        $handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
        $command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);

        $result = $handler->__invoke($ticket, $command);

        // Assert that the ticket is returned
        $this->assertSame($ticket, $result);

        // Assert that the ticket emergency status is now YES
        $this->assertSame(EmergencyStatusEnum::YES, $ticket->getEmergencyStatus());

        // Assert that the old emergency status history was ended and a new one was created
        $emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
        $this->assertCount(2, $emergencyStatusHistories);

        // The first emergency status history should be ended
        $noEmergencyStatusHistory = $emergencyStatusHistories->first();
        $this->assertNotNull($noEmergencyStatusHistory->getEndDate());
        $this->assertSame(EmergencyStatusEnum::NO, $noEmergencyStatusHistory->getEmergencyStatus());

        // The last emergency status history should be YES and active
        $yesEmergencyStatusHistory = $emergencyStatusHistories->last();
        $this->assertNull($yesEmergencyStatusHistory->getEndDate());
        $this->assertSame(EmergencyStatusEnum::YES, $yesEmergencyStatusHistory->getEmergencyStatus());
    }
}
