<?php

declare(strict_types=1);

namespace App\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\BaseCronJob;
use App\Service\File\Storage\LocalStorage;
use App\Service\Utils\DatesUtils;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\QueryBuilder;
use JetBrains\PhpStorm\Pure;

/**
 * Cronjob that delete temporary files older than N days.
 *
 * >>> ot:cron run clean-temp-files --preview
 * >>> ot:cron run clean-temp-files
 */
class CleanTempFiles extends BaseCronJob
{
    /**
     * Delay before removing a temporary file (in days).
     */
    private const DELETE_OLDER_THAN = 60;

    #[Pure]
    public function __construct(
        private Connection $connection,
        private FileRepository $fileRepository,
        private LocalStorage $storage,
    ) {
        parent::__construct();
    }

    /**
     * Preview the result of the execution, without actually deleting anything.
     *
     * @throws \Exception
     */
    public function preview(): void
    {
        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P'.self::DELETE_OLDER_THAN.'D'));

        $files = $this->listFilesToDelete($maxDate);
        $total = count($files);
        $this->ui->print($total.' temporary files to be removed');
        $this->ui->print('> Printing the first and last 50 :');

        $i = 0;
        foreach ($files as $file) {
            ++$i;
            if ($i > 50 && ($total - $i) > 50) {
                continue;
            }
            if (($total - $i) === 50) {
                $this->ui->print('  (...)');
            }
            $this->ui->print('  * '.$file->getPath());
        }
    }

    /**
     * Proceed to the deletion of the files and the purge of the DB.
     *
     * @throws \Exception
     */
    public function execute(): void
    {
        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P'.self::DELETE_OLDER_THAN.'D'));

        $files = $this->listFilesToDelete($maxDate);
        $this->logger->info(count($files).' temporary files to be removed');

        $this->deleteFiles($files);
    }

    /**
     * List the files to delete in the DB.
     *
     * @return array<File>
     *
     * @throws \Exception
     */
    protected function listFilesToDelete(\DateTime $maxDate): array
    {
        $this->ui->print('List temporary files created before '.$maxDate->format('c'));

        $queryBuilder = $this->fileRepository->createQueryBuilder('f');
        $queryBuilder->select();
        $this->getQueryConditions($queryBuilder, $maxDate);

        return $queryBuilder->getQuery()->getResult();
    }

    /**
     * Delete the files.
     *
     * @param array<File> $files
     */
    protected function deleteFiles(array $files): void
    {
        $total = count($files);
        $this->logger->info($total.' temporary files to be removed');

        $this->connection->setAutoCommit(false);
        $queryBuilder = $this->fileRepository->createQueryBuilder('f');

        $this->logger->info('Deleting files...');
        $i = 0;
        $deleted = 0;
        $this->ui->progress(0, $total);

        foreach ($files as $file) {
            $this->connection->beginTransaction();
            $this->ui->progress($i, $total);
            ++$i;

            try {
                // Delete from disk
                $this->storage->hardDelete($file);

                // Remove from DB
                $queryBuilder->delete()->where('f.id = :id')->setParameter('id', $file->getId());

                $this->connection->commit();
                ++$deleted;
            } catch (\RuntimeException|\InvalidArgumentException $e) {
                // Non blocking errors
                $this->connection->rollback();
                $this->logger->error('ERROR : '.$e->getMessage());
            } catch (\Exception $exception) {
                $this->connection->rollback();
                throw $exception;
            }
        }

        $this->logger->info($deleted.' files deleted');
    }

    protected function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void
    {
        $queryBuilder
            ->andWhere(
                $queryBuilder->expr()->orX(
                    $queryBuilder->expr()->eq('f.isTemporaryFile', ':temporaryTrue'),
                    $queryBuilder->expr()->eq('f.status', ':status')
                )
            )
            ->andWhere($queryBuilder->expr()->eq('f.host', ':host'))
            ->andWhere(
                $queryBuilder->expr()->orX(
                    $queryBuilder->expr()->lt('f.createDate', ':maxDate'),
                    $queryBuilder->expr()->isNull('f.createDate')
                )
            )
            ->setParameter('temporaryTrue', true)
            ->setParameter('host', FileHostEnum::AP2I)
            ->setParameter('status', FileStatusEnum::DELETED)
            ->setParameter('maxDate', $maxDate->format('Y-m-d'))
        ;
    }
}
