<?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 ChillMainBundle\Tests\Services\Workflow;

use Chill\MainBundle\Entity\CronJobExecution;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowCronJob;
use Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class CancelStaleWorkflowCronJobTest extends TestCase
{
    /**
     * @dataProvider buildTestCanRunData
     *
     * @throws \Exception
     */
    public function testCanRun(?CronJobExecution $cronJobExecution, bool $expected): void
    {
        $clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00')));
        $logger = $this->createMock(LoggerInterface::class);
        $entityManager = $this->createMock(EntityManagerInterface::class);

        $cronJob = new CancelStaleWorkflowCronJob($this->createMock(EntityWorkflowRepository::class), $clock, $this->buildMessageBus(), $logger, $entityManager);

        self::assertEquals($expected, $cronJob->canRun($cronJobExecution));
    }

    /**
     * @throws \Exception
     */
    public static function buildTestCanRunData(): iterable
    {
        yield [
            (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:00', new \DateTimeZone('+00:00'))),
            true,
        ];

        yield [
            (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-30 23:59:59', new \DateTimeZone('+00:00'))),
            true,
        ];

        yield [
            (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:01', new \DateTimeZone('+00:00'))),
            false,
        ];

        yield [
            null,
            true,
        ];
    }

    /**
     * @throws \DateMalformedStringException
     * @throws \DateInvalidTimeZoneException
     * @throws \Exception|Exception
     */
    public function testRun(): void
    {
        $clock = new MockClock((new \DateTimeImmutable('now', new \DateTimeZone('+00:00')))->add(new \DateInterval('P120D')));
        $workflowRepository = $this->createMock(EntityWorkflowRepository::class);
        $entityManager = $this->createMock(EntityManagerInterface::class);

        $workflowRepository->method('findWorkflowsWithoutFinalStepAndOlderThan')->willReturn([
            $this->buildEntityWorkflow(1),
            $this->buildEntityWorkflow(3),
            $this->buildEntityWorkflow(2),
        ]);
        $messageBus = $this->buildMessageBus(true);

        $cronJob = new CancelStaleWorkflowCronJob($workflowRepository, $clock, $messageBus, new NullLogger(), $entityManager);

        $results = $cronJob->run([]);

        // Assert the result has the last canceled workflow ID
        self::assertArrayHasKey('last-canceled-workflow-id', $results);
        self::assertEquals(3, $results['last-canceled-workflow-id']);
    }

    private function buildEntityWorkflow(int $id): EntityWorkflow
    {
        $entityWorkflow = new EntityWorkflow();
        $reflectionClass = new \ReflectionClass($entityWorkflow);
        $idProperty = $reflectionClass->getProperty('id');
        $idProperty->setValue($entityWorkflow, $id);

        return $entityWorkflow;
    }

    private function buildMessageBus(bool $expectDispatchAtLeastOnce = false): MessageBusInterface
    {
        $messageBus = $this->createMock(MessageBusInterface::class);

        $methodDispatch = match ($expectDispatchAtLeastOnce) {
            true => $messageBus->expects($this->atLeastOnce())->method('dispatch')->with($this->isInstanceOf(CancelStaleWorkflowMessage::class)),
            false => $messageBus->method('dispatch'),
        };

        $methodDispatch->willReturnCallback(fn (CancelStaleWorkflowMessage $message) => new Envelope($message));

        return $messageBus;
    }
}
