<?php

namespace App\Service\Cron\Job;

use App\Repository\Access\AccessRepository;
use App\Repository\Core\FileRepository;
use App\Service\Cron\BaseCronJob;
use App\Service\Cron\CronjobInterface;
use App\Service\File\Storage\LocalStorage;
use App\Service\Utils\DatesUtils;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use JetBrains\PhpStorm\Pure;

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

    /**
     * ID of the Access who author the file deletion
     */
    private const AUTHOR = 10984;

    /**
     * Delay before deleting the record of the temporary file from the DB
     */
    private const PURGE_RECORDS_OLDER_THAN = 365;

    /**
     * @param FileRepository $fileRepository
     * @param EntityManagerInterface $em
     * @param AccessRepository $accessRepository
     * @param LocalStorage $storage
     */
    #[Pure]
    public function __construct(
        private FileRepository $fileRepository,
        private EntityManagerInterface $em,
        private AccessRepository $accessRepository,
        private LocalStorage $storage
    ) {
        parent::__construct();
    }

    /**
     * Preview the result of the execution, without actually deleting anything
     * @throws Exception
     */
    public function preview(): void
    {
        $files = $this->listFilesToDelete();
        $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());
        }

        $this->purgeDb(false);
    }

    /**
     * Proceed to the deletion of the files and the purge of the DB
     * @throws Exception
     */
    public function execute(): void
    {
        $files = $this->listFilesToDelete();

        $this->deleteFiles($files);

        $this->purgeDb();
    }

    /**
     * List the files to delete in the DB
     *
     * @return Collection
     * @throws Exception
     */
    protected function listFilesToDelete(): Collection
    {

        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P' . self::DELETE_OLDER_THAN . 'D'));

        $this->ui->print('List temporary files created before ' . $maxDate->format('c'));

        $criteria = new Criteria();
        $criteria->where(
            Criteria::expr()->andX(
                Criteria::expr()->eq('isTemporaryFile', true),
                Criteria::expr()->orX(
                    Criteria::expr()->lt('createDate', $maxDate),
                    Criteria::expr()->isNull('createDate')
                )
            )
        );

        return $this->fileRepository->matching($criteria);
    }

    /**
     * Delete the files
     *
     * @param Collection $files
     */
    protected function deleteFiles(Collection $files): void {
        $total = count($files);
        $this->ui->print($total . " temporary files to be removed");

        $author = $this->accessRepository->find(self::AUTHOR);

        if ($author === null) {
            throw new \RuntimeException('Access ' . self::AUTHOR . ' could not be found');
        }

        $this->ui->print('Deleting files...');
        $i = 0;
        $deleted = 0;
        $this->ui->progress(0, $total);
        foreach ($files as $file) {
            try {
                $i++;
                $this->ui->progress($i, $total);
                $this->storage->delete($file, $author);
                $deleted++;
            } catch (\RuntimeException $e) {
                $this->ui->print('ERROR : ' . $e->getMessage());
            }
        }

        $this->ui->print($deleted . ' files deleted');
    }

    /**
     * Purge the DB from temporary file records older than N days
     *
     * @param bool $commit
     * @throws Exception
     */
    protected function purgeDb(bool $commit = true): void {

        $maxDate = DatesUtils::new();
        $maxDate->sub(new \DateInterval('P' . self::PURGE_RECORDS_OLDER_THAN . 'D'));

        $this->ui->print('Purge DB from records of files deleted before ' . $maxDate->format('c'));

        $this->em->beginTransaction();

        $queryBuilder = $this->em->createQueryBuilder();
        $q = $queryBuilder
            ->delete('File', 'f')
            ->where($queryBuilder->expr()->eq('f.isTemporaryFile', 1))
            ->andWhere($queryBuilder->expr()->lt('f.updateDate', ':maxDate'))
            ->setParameter('maxDate', $maxDate)
            ->getQuery();

        $purged = $q->getResult();

        if ($commit) {
            $this->em->commit();
            $this->ui->print('DB purged - ' . $purged . ' records permanently deleted');
            return;
        }

        $this->em->rollback();
        $this->ui->print('DB purged - ' . $purged . ' records would be permanently deleted');
    }

}
