<?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\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
use Chill\TicketBundle\Entity\CallerHistory;
use Chill\TicketBundle\Entity\Ticket;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;

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

    private \DateTimeImmutable $now;
    private ClockInterface $clock;
    private SetCallerCommandHandler $handler;

    protected function setUp(): void
    {
        $this->now = new \DateTimeImmutable('2023-01-01 12:00:00');
        $this->clock = new MockClock($this->now);
        $this->handler = new SetCallerCommandHandler($this->clock);
    }

    public function testSetPersonAsCaller(): void
    {
        // Arrange
        $ticket = new Ticket();
        $person = new Person();
        $command = new SetCallerCommand($person);

        // Act
        $result = ($this->handler)($ticket, $command);

        // Assert
        self::assertSame($ticket, $result);
        self::assertSame($person, $ticket->getCaller());
        self::assertCount(1, $ticket->getCallerHistories());

        $callerHistory = $ticket->getCallerHistories()->first();
        self::assertInstanceOf(CallerHistory::class, $callerHistory);
        self::assertSame($person, $callerHistory->getPerson());
        self::assertNull($callerHistory->getThirdParty());
        self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
        self::assertNull($callerHistory->getEndDate());
    }

    public function testSetThirdPartyAsCaller(): void
    {
        // Arrange
        $ticket = new Ticket();
        $thirdParty = new ThirdParty();
        $command = new SetCallerCommand($thirdParty);

        // Act
        $result = ($this->handler)($ticket, $command);

        // Assert
        self::assertSame($ticket, $result);
        self::assertSame($thirdParty, $ticket->getCaller());
        self::assertCount(1, $ticket->getCallerHistories());

        $callerHistory = $ticket->getCallerHistories()->first();
        self::assertInstanceOf(CallerHistory::class, $callerHistory);
        self::assertNull($callerHistory->getPerson());
        self::assertSame($thirdParty, $callerHistory->getThirdParty());
        self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
        self::assertNull($callerHistory->getEndDate());
    }

    public function testChangeCallerFromPersonToThirdParty(): void
    {
        // Arrange
        $ticket = new Ticket();
        $person = new Person();
        $thirdParty = new ThirdParty();

        // Set initial person caller
        $initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
        $initialCallerHistory->setPerson($person);

        // Create command to change to third party
        $command = new SetCallerCommand($thirdParty);

        // Act
        $this->clock->modify('+ 10 minutes');
        $result = ($this->handler)($ticket, $command);

        // Assert
        self::assertSame($ticket, $result);
        self::assertSame($thirdParty, $ticket->getCaller());
        self::assertCount(2, $ticket->getCallerHistories());

        // Check that the first history is ended
        $firstCallerHistory = $ticket->getCallerHistories()->first();
        self::assertSame($person, $firstCallerHistory->getPerson());
        self::assertEquals($this->clock->now()->getTimestamp(), $firstCallerHistory->getEndDate()->getTimestamp());

        // Check that the new history is created correctly
        $lastCallerHistory = $ticket->getCallerHistories()->last();
        self::assertNull($lastCallerHistory->getPerson());
        self::assertSame($thirdParty, $lastCallerHistory->getThirdParty());
        self::assertSame($this->clock->now()->getTimestamp(), $lastCallerHistory->getStartDate()->getTimestamp());
        self::assertNull($lastCallerHistory->getEndDate());
    }

    public function testRemoveCaller(): void
    {
        // Arrange
        $ticket = new Ticket();
        $person = new Person();

        // Set initial person caller
        $initialCallerHistory = new CallerHistory($person, $ticket, $this->now);

        // Create command to remove caller
        $command = new SetCallerCommand(null);

        // Act
        $result = ($this->handler)($ticket, $command);

        // Assert
        self::assertSame($ticket, $result);
        self::assertNull($ticket->getCaller());
        self::assertCount(2, $ticket->getCallerHistories());

        // Check that the history is ended
        $callerHistory = $ticket->getCallerHistories()->first();
        self::assertSame($person, $callerHistory->getPerson());
        self::assertEquals($this->now->getTimestamp(), $callerHistory->getEndDate()->getTimestamp());
    }

    public function testNoChangeWhenCallerIsAlreadySet(): void
    {
        // Arrange
        $ticket = new Ticket();
        $person = new Person();

        // Set initial person caller
        $initialCallerHistory = new CallerHistory($person, $ticket, $this->now);

        // Create command with the same person
        $command = new SetCallerCommand($person);

        // Act
        $result = ($this->handler)($ticket, $command);

        // Assert
        self::assertSame($ticket, $result);
        self::assertSame($person, $ticket->getCaller());
        self::assertCount(1, $ticket->getCallerHistories());

        // Check that the history is unchanged
        $callerHistory = $ticket->getCallerHistories()->first();
        self::assertSame($person, $callerHistory->getPerson());
        self::assertNull($callerHistory->getEndDate());
    }
}
