<?php

namespace App\Tests\Unit\Service\Cron\Job;

use App\Entity\Core\File;
use App\Enum\Core\FileHostEnum;
use App\Enum\Core\FileStatusEnum;
use App\Repository\Core\FileRepository;
use App\Service\Cron\Job\CleanTempFiles;
use App\Service\Cron\UI\CronUIInterface;
use App\Service\File\Storage\LocalStorage;
use App\Service\Utils\DatesUtils;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Definition\Builder\ExprBuilder;

class TestableCleanTempFile extends CleanTempFiles {
    public function listFilesToDelete(\DateTime $maxDate): Collection { return parent::listFilesToDelete($maxDate); }
    public function deleteFiles(Collection $files): void { parent::deleteFiles($files); }
    public function purgeDb(\DateTime $maxDate, bool $commit = true): void { parent::purgeDb($maxDate, $commit); }
    public function purgeFiles(\DateTime $maxDate): int { return parent::purgeFiles($maxDate); }
    public function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void { parent::getQueryConditions($queryBuilder, $maxDate); }
}

class CleanTempFilesTest extends TestCase
{
    private CronUIInterface|MockObject $ui;
    private MockObject|LoggerInterface $logger;
    private FileRepository|MockObject $fileRepository;
    private Connection|MockObject $connection;
    private LocalStorage|MockObject $storage;

    public function setUp(): void
    {
        $this->ui = $this->getMockBuilder(CronUIInterface::class)->disableOriginalConstructor()->getMock();
        $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();

        $this->fileRepository = $this->getMockBuilder(FileRepository::class)->disableOriginalConstructor()->getMock();
        $this->connection = $this->getMockBuilder(Connection::class)->disableOriginalConstructor()->getMock();
        $this->storage = $this->getMockBuilder(LocalStorage::class)->disableOriginalConstructor()->getMock();
    }

    private function getMockFor(string $method) {
        $cleanTempFiles = $this->getMockBuilder(TestableCleanTempFile::class)
            ->setConstructorArgs([$this->connection, $this->fileRepository, $this->storage])
            ->setMethodsExcept([$method, 'setUI', 'setLoggerInterface'])
            ->getMock();
        $cleanTempFiles->setUI($this->ui);
        $cleanTempFiles->setLoggerInterface($this->logger);

        return $cleanTempFiles;
    }

    public function testPreview(): void
    {
        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P60D'));

        $cleanTempFiles = $this->getMockFor('preview');

        $file1 = $this->getMockBuilder(File::class)->getMock();
        $file1->method('getPath')->willReturn('/foo');

        $file2 = $this->getMockBuilder(File::class)->getMock();
        $file2->method('getPath')->willReturn('/bar');

        $file3 = $this->getMockBuilder(File::class)->getMock();
        $file3->method('getPath')->willReturn('/foo/bar');

        $cleanTempFiles->method('listFilesToDelete')->with($maxDate)->willReturn(new ArrayCollection([$file1, $file2, $file3]));

        $this->ui->expects(self::atLeastOnce())->method('print')->withConsecutive(
            ["3 temporary files to be removed"],
            ["> Printing the first and last 50 :"],
            ["  * /foo"],
            ["  * /bar"],
            ["  * /foo/bar"]
        );

        $cleanTempFiles->expects(self::once())->method('purgeDb')->with($maxDate, false);

        $cleanTempFiles->preview();
    }

    public function testExecute(): void {
        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P60D'));

        $cleanTempFiles = $this->getMockFor('execute');

        $files = new ArrayCollection([
            $this->getMockBuilder(File::class)->getMock(),
            $this->getMockBuilder(File::class)->getMock(),
            $this->getMockBuilder(File::class)->getMock()
        ]);

        $cleanTempFiles->method('listFilesToDelete')->willReturn($files)->with($maxDate);

        $cleanTempFiles->expects(self::once())->method('deleteFiles')->with($files);

        $cleanTempFiles->expects(self::once())->method('purgeDb')->with($maxDate);

        $cleanTempFiles->execute();
    }

    public function testListFilesToDelete()
    {
        $cleanTempFiles = $this->getMockFor('listFilesToDelete');

        DatesUtils::setFakeDatetime('2023-05-01 00:00:00');
        $maxDate = DatesUtils::new();

        // Mock la méthode getQueryBuilder()
        $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->fileRepository->method('createQueryBuilder')->with('f')->willReturn($queryBuilder);

        // S'attend à ce que la méthode select() soit appelée
        $queryBuilder->expects($this->once())
            ->method('select');

        // S'attend à ce que la méthode getQueryConditions() soit appelée avec $maxDate
        $cleanTempFiles->expects($this->once())
            ->method('getQueryConditions')
            ->with($queryBuilder, $maxDate);

        // Mock la méthode getQuery() et getResult() pour renvoyer un tableau vide
        $query = $this->getMockBuilder(AbstractQuery::class)
            ->disableOriginalConstructor()
            ->getMock();
        $query->expects($this->once())
            ->method('getResult')
            ->willReturn(new ArrayCollection());

        $queryBuilder->expects($this->once())
            ->method('getQuery')
            ->willReturn($query);

        // Appeler la méthode listFilesToDelete()
        $result = $cleanTempFiles->listFilesToDelete($maxDate);

        // Vérifier que la méthode getResult() a été appelée sur la requête
        $this->assertInstanceOf(Collection::class, $result);
        $this->assertEquals(0, $result->count());
    }

    public function testPurgeDbCommitsTransactionIfCommitIsTrue(): void
    {
        $cleanTempFiles = $this->getMockFor('purgeDb');

        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = new \DateTime('now');

        $this->connection->expects($this->once())
            ->method('beginTransaction');
        $this->connection->expects($this->once())
            ->method('setAutoCommit')
            ->with(false);

        $cleanTempFiles->method('purgeFiles')->willReturn(5)->with($maxDate);

        $this->connection->expects($this->once())
            ->method('commit');

        $this->ui->expects($this->once())
            ->method('print')
            ->with('DB purged - 5 records permanently deleted');

        $cleanTempFiles->purgeDb($maxDate);
    }

    public function testPurgeDbRollsbackTransactionIfCommitIsFalse(): void
    {
        $cleanTempFiles = $this->getMockFor('purgeDb');

        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = DatesUtils::new();

        $this->connection->expects($this->once())
            ->method('beginTransaction');
        $this->connection->expects($this->once())
            ->method('setAutoCommit')
            ->with(false);

        $cleanTempFiles->method('purgeFiles')->willReturn(5)->with($maxDate);

        $this->connection->expects($this->once())
            ->method('rollback');
        $this->ui->expects($this->once())
            ->method('print')
            ->with('DB purged - 5 records would be permanently deleted');

        $cleanTempFiles->purgeDb($maxDate, false);
    }

    public function testPurgeDbRollsbackTransactionOnException(): void
    {
        $cleanTempFiles = $this->getMockFor('purgeDb');

        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = DatesUtils::new();

        $cleanTempFiles->method('purgeFiles')->willThrowException(new \Exception('error'))->with($maxDate);

        $this->connection->expects($this->once())
            ->method('beginTransaction');
        $this->connection->expects($this->once())
            ->method('rollback');
        $this->ui->expects($this->never())
            ->method('print');

        $this->expectException(\Exception::class);
        $cleanTempFiles->purgeDb($maxDate, true);
    }


    public function testPurgeFilesDeletes()
    {
        $cleanTempFiles = $this->getMockFor('purgeFiles');

        DatesUtils::setFakeDatetime('2022-01-08 00:00:00');
        $maxDate = DatesUtils::new();

        $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
            ->disableOriginalConstructor()
            ->getMock();
        $queryBuilder->expects($this->once())
            ->method('delete')
            ->willReturnSelf();

        $query = $this->getMockBuilder(AbstractQuery::class)
            ->disableOriginalConstructor()
            ->getMock();
        $queryBuilder->expects($this->once())
            ->method('getQuery')
            ->willReturn($query);
        $query->expects($this->once())
            ->method('execute')
            ->willReturn(3);

        $this->fileRepository->method('createQueryBuilder')->with('f')->willReturn($queryBuilder);

        $this->assertEquals(3, $cleanTempFiles->purgeFiles($maxDate));
    }
}
