<?php

declare(strict_types=1);

namespace App\Service\Export;

use App\ApiResources\Export\ExportRequest;
use App\Entity\Core\File;
use App\Enum\Core\FileTypeEnum;
use App\Enum\Core\FileVisibilityEnum;
use App\Repository\Access\AccessRepository;
use App\Repository\Core\FileRepository;
use App\Service\Export\Model\ExportModelInterface;
use App\Service\File\FileManager;
use App\Service\ServiceIterator\EncoderIterator;
use App\Service\Utils\FileUtils;
use App\Service\Utils\StringsUtils;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\Attribute\Required;
use Twig\Environment;

/**
 * Classe de base des services d'export.
 */
abstract class BaseExporter
{
    // dependencies injections
    protected AccessRepository $accessRepository;
    protected FileRepository $fileRepository;
    protected Environment $twig;
    protected EncoderIterator $encoderIterator;
    protected EntityManagerInterface $entityManager;
    protected LoggerInterface $logger;
    protected FileManager $fileManager;
    protected FileUtils $fileUtils;

    #[Required]
    public function setAccessRepository(AccessRepository $accessRepository): void
    {
        $this->accessRepository = $accessRepository;
    }

    #[Required]
    public function setFileRepository(FileRepository $fileRepository): void
    {
        $this->fileRepository = $fileRepository;
    }

    #[Required]
    public function setTwig(Environment $twig): void
    {
        $this->twig = $twig;
    }

    #[Required]
    public function setEncoderIterator(EncoderIterator $encoderIterator): void
    {
        $this->encoderIterator = $encoderIterator;
    }

    #[Required]
    public function setEntityManager(EntityManagerInterface $entityManager): void
    {
        $this->entityManager = $entityManager;
    }

    #[Required]
    public function setFileManager(FileManager $fileManager): void
    {
        $this->fileManager = $fileManager;
    }

    #[Required]
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    #[Required]
    public function setFileUtils(FileUtils $fileUtils): void
    {
        $this->fileUtils = $fileUtils;
    }

    public function support(ExportRequest $exportRequest): bool
    {
        return false;
    }

    /**
     * Exécute l'opération d'export correspondant à la requête passée
     * en paramètre.
     */
    public function export(ExportRequest $exportRequest): File
    {
        $requesterId = $exportRequest->getRequesterId();
        $requester = $this->accessRepository->find($requesterId);
        if ($requester === null) {
            throw new \RuntimeException('Unable to determine the user; abort.');
        }

        // Génère le modèle à partir de l'exportRequest
        $model = $this->buildModel($exportRequest);

        // Génère le html à partir du template et du service
        $html = $this->render($model);

        // Encode le html au format voulu
        $content = $this->encode($html, $exportRequest->getFormat()->value);

        // Met à jour ou créé l'enregistrement du fichier en base
        if ($exportRequest->getFileId() !== null) {
            $file = $this->fileRepository->find($exportRequest->getFileId());
        } else {
            // Todo: voir si ce else est nécessaire une fois tous les exports implémentés (c'est
            //       juste au cas où le record File n'existe pas encore en base)
            $organization = $requester->getOrganization();
            if ($organization === null) {
                throw new \RuntimeException('Unable to determine the organization of the curent user; abort.');
            }
            $file = $this->prepareFile($exportRequest, false);
        }

        return $this->fileManager->write($file, $content, $requester);
    }

    /**
     * Create a pending file record in the database.
     */
    public function prepareFile(ExportRequest $exportRequest, bool $flushFile = true): File
    {
        $requesterId = $exportRequest->getRequesterId();
        $requester = $this->accessRepository->find($requesterId);
        if ($requester === null) {
            throw new \RuntimeException('Unable to determine the current user; abort.');
        }

        $filename = $this->getFileBasename($exportRequest);
        if (!preg_match('/^.+\.'.$exportRequest->getFormat()->value.'$/i', $filename)) {
            $filename .= '.'.$exportRequest->getFormat()->value;
        }

        return $this->fileManager->prepareFile(
            $requester,
            $filename,
            $this->getFileType(),
            $requester,
            true,
            FileVisibilityEnum::NOBODY,
            $this->fileUtils->getMimeTypeFromExt($exportRequest->getFormat()->value),
            $flushFile
        );
    }

    /**
     * Construit le modèle de données qui servira au render du template.
     */
    public function buildModel(ExportRequest $exportRequest): ExportModelInterface
    {
        throw new \RuntimeException('not implemented error');
    }

    /**
     * Retourne le nom par défaut de cet export,
     * utilisé pour trouver le template twig ou encore pour nommer
     * le fichier exporté.
     */
    protected function getBasename(): string
    {
        $arr = explode('\\', static::class);
        $classname = end($arr);

        return StringsUtils::camelToSnake(
            preg_replace(
                '/^([\w]+)Exporter$/',
                '$1',
                $classname,
                1)
        );
    }

    /**
     * Return the path of the twig template for this export.
     */
    protected function getTemplatePath(): string
    {
        return '@templates/export/'.$this->getBasename().'.html.twig';
    }

    /**
     * Génère le rendu HTML à partir du template twig et du modèle de données.
     *
     * Attention : Les liens vers les fichiers statiques ou dynamiques doivent être des chemins d'accès absolus pour
     *             être traités par wkhtmltoX; Les images peuvent être incluses au format base64;
     *
     *             @see App\Service\Twig\AssetsExtension
     *
     * @return string Rendu HTML
     */
    protected function render(ExportModelInterface $model): string
    {
        try {
            return $this->twig->render(
                $this->getTemplatePath(),
                ['model' => $model]
            );
        } catch (\Twig\Error\LoaderError|\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) {
            throw new \RuntimeException('error during template rendering : '.$e);
        }
    }

    /**
     * Encode le html au format demandé.
     *
     * @param string $format @see ExportFormatEnum
     */
    protected function encode(string $html, string $format): string
    {
        return $this->encoderIterator->getEncoderFor($format)->encode($html);
    }

    /**
     * Retourne le nom du fichier exporté.
     */
    protected function getFileBasename(ExportRequest $exportRequest): string
    {
        return $this->getBasename();
    }

    /**
     * Retourne le type de fichier tel qu'il apparait au niveau du champ File.type.
     */
    protected function getFileType(): FileTypeEnum
    {
        return FileTypeEnum::UNKNOWN;
    }
}
