<?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 Tests\Service\StoredObjectCleaner;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Chill\DocStoreBundle\Service\StoredObjectCleaner\RemoveExpiredStoredObjectCronJob;
use Chill\DocStoreBundle\Service\StoredObjectCleaner\RemoveOldVersionMessage;
use Chill\MainBundle\Entity\CronJobExecution;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

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

        $cronJob = new RemoveExpiredStoredObjectCronJob($clock, $this->buildMessageBus(), $repository);

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

    public static function buildTestCanRunData(): iterable
    {
        yield [
            (new CronJobExecution('remove-expired-stored-object'))->setLastEnd(new \DateTimeImmutable('2023-12-25 00:00:00', new \DateTimeZone('+00:00'))),
            true,
        ];

        yield [
            (new CronJobExecution('remove-expired-stored-object'))->setLastEnd(new \DateTimeImmutable('2023-12-24 23:59:59', new \DateTimeZone('+00:00'))),
            true,
        ];

        yield [
            (new CronJobExecution('remove-expired-stored-object'))->setLastEnd(new \DateTimeImmutable('2023-12-25 00:00:01', new \DateTimeZone('+00:00'))),
            false,
        ];

        yield [
            null,
            true,
        ];
    }

    public function testRun(): void
    {
        $repository = $this->createMock(StoredObjectRepositoryInterface::class);
        $repository->expects($this->atLeastOnce())->method('findByExpired')->withAnyParameters()->willReturnCallback(
            function (\DateTimeImmutable $date): iterable {
                yield $this->buildStoredObject(3);
                yield $this->buildStoredObject(1);
            }
        );
        $clock = new MockClock();

        $cronJob = new RemoveExpiredStoredObjectCronJob($clock, $this->buildMessageBus(true), $repository);

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

        self::assertEquals(3, $actual['last-deleted-stored-object-id']);
    }

    private function buildStoredObject(int $id): StoredObject
    {
        $object = new StoredObject();
        $object->registerVersion();
        $class = new \ReflectionClass($object);
        $idProperty = $class->getProperty('id');
        $idProperty->setValue($object, $id);

        $classVersion = new \ReflectionClass($object->getCurrentVersion());
        $idPropertyVersion = $classVersion->getProperty('id');
        $idPropertyVersion->setValue($object->getCurrentVersion(), $id);

        return $object;
    }

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

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

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

        return $messageBus;
    }
}
