<?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\Repository\Access\AccessRepository;
use App\Repository\Core\FileRepository;
use App\Service\Export\Model\ExportModelInterface;
use App\Service\File\FileManager;
use App\Service\File\Storage\LocalStorage;
use App\Service\ServiceIterator\EncoderIterator;
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;
    private LocalStorage $localStorage;
    protected FileManager $fileManager;

    #[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 setLocalStorage(LocalStorage $localStorage): void
    { $this->localStorage = $localStorage; }
    #[Required]
    public function setFileManager(FileManager $fileManager): void
    { $this->fileManager = $fileManager; }
    #[Required]
    public function setLogger(LoggerInterface $logger): void
    { $this->logger = $logger; }

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

    /**
     * Exécute l'opération d'export correspondant à la requête passée
     * en paramètre
     *
     * @param ExportRequest $exportRequest
     * @return File
     */
    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());

        // 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);
        }

        // TODO: passer par le FileManager plutôt?
        return $this->localStorage->writeFile($file, $content, $requester);
    }

    /**
     * Create a pending file record in the database
     *
     * @param ExportRequest $exportRequest
     * @param bool $flushFile
     * @return File
     */
    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() . '$/i', $filename)) {
            $filename .= '.' . $exportRequest->getFormat();
        }

        return $this->localStorage->prepareFile(
            $requester,
            $filename,
            $this->getFileType(),
            $requester,
            true,
            'NOBODY',
            FileManager::getMimeTypeFromExt($exportRequest->getFormat()),
            $flushFile
        );
    }

    /**
     * Construit le modèle de données qui servira au render du template
     *
     * @param ExportRequest $exportRequest
     * @return ExportModelInterface
     */
    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é.
     *
     * @return string
     */
    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
     * @return string
     */
    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
     *
     * @param ExportModelInterface $model
     * @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 $html
     * @param string $format @see ExportFormatEnum
     * @return string
     */
    protected function encode(string $html, string $format): string
    {
        return $this->encoderIterator->getEncoderFor($format)->encode($html);
    }

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

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