<?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\ChangeStateCommand;
use Chill\TicketBundle\Action\Ticket\Handler\ChangeStateCommandHandler;
use Chill\TicketBundle\Entity\StateEnum;
use Chill\TicketBundle\Entity\StateHistory;
use Chill\TicketBundle\Entity\Ticket;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;

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

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

        // Create a closed state history
        new StateHistory(StateEnum::CLOSED, $ticket);

        $handler = new ChangeStateCommandHandler(new MockClock());
        $command = new ChangeStateCommand(StateEnum::CLOSED);

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

        // Assert that the ticket is returned unchanged
        $this->assertSame($ticket, $result);
        $this->assertSame(StateEnum::CLOSED, $ticket->getState());

        // Assert that no new state history was created
        $stateHistories = $ticket->getStateHistories();
        $this->assertCount(1, $stateHistories);
    }

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

        // Create an open state history
        new StateHistory(StateEnum::OPEN, $ticket);

        $handler = new ChangeStateCommandHandler(new MockClock());
        $command = new ChangeStateCommand(StateEnum::CLOSED);

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

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

        // Assert that the ticket state is now closed
        $this->assertSame(StateEnum::CLOSED, $ticket->getState());

        // Assert that the old state history was ended and a new one was created
        $stateHistories = $ticket->getStateHistories();
        $this->assertCount(2, $stateHistories);

        // The first state history should be ended
        $openStateHistory = $stateHistories->first();
        $this->assertNotNull($openStateHistory->getEndDate());
        $this->assertSame(StateEnum::OPEN, $openStateHistory->getState());

        // The last state history should be closed and active
        $closedStateHistory = $stateHistories->last();
        $this->assertNull($closedStateHistory->getEndDate());
        $this->assertSame(StateEnum::CLOSED, $closedStateHistory->getState());
    }

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

        // Create a closed state history
        new StateHistory(StateEnum::CLOSED, $ticket);

        $handler = new ChangeStateCommandHandler(new MockClock());
        $command = new ChangeStateCommand(StateEnum::OPEN);

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

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

        // Assert that the ticket state is now open
        $this->assertSame(StateEnum::OPEN, $ticket->getState());

        // Assert that the old state history was ended and a new one was created
        $stateHistories = $ticket->getStateHistories();
        $this->assertCount(2, $stateHistories);

        // The first state history should be ended
        $closedStateHistory = $stateHistories->first();
        $this->assertNotNull($closedStateHistory->getEndDate());
        $this->assertSame(StateEnum::CLOSED, $closedStateHistory->getState());

        // The last state history should be open and active
        $openStateHistory = $stateHistories->last();
        $this->assertNull($openStateHistory->getEndDate());
        $this->assertSame(StateEnum::OPEN, $openStateHistory->getState());
    }
}
