<?php

declare(strict_types=1);

namespace App\Service\File;

use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface as UrlGeneratorInterfaceApiPlatform;
use App\Entity\Access\Access;
use App\Entity\Core\File;
use App\Entity\Organization\Organization;
use App\Entity\Person\Person;
use App\Enum\Core\FileTypeEnum;
use App\Enum\Core\FileVisibilityEnum;
use App\Repository\Core\FileRepository;
use App\Service\File\Exception\FileNotFoundException;
use App\Service\File\Factory\ImageFactory;
use App\Service\File\Storage\FileStorageInterface;
use App\Service\File\Storage\LocalStorage;
use App\Service\ServiceIterator\StorageIterator;
use Doctrine\ORM\EntityManagerInterface;

/**
 * Le gestionnaire de fichiers permet d'effectuer de nombreuses opérations sur les fichiers stockés dans les différents
 * FileStorage (lecture, écriture, suppression...).
 */
class FileManager
{
    public function __construct(
        protected readonly IriConverterInterface $iriConverter,
        protected readonly StorageIterator $storageIterator,
        protected readonly ImageFactory $imageFactory,
        protected readonly LocalStorage $localStorage,
        protected readonly EntityManagerInterface $entityManager,
        protected readonly FileRepository $fileRepository,
    ) {
    }

    /**
     * Retourne le storage dans lequel le fichier demandé est supposé se trouver.
     *
     * @throws FileNotFoundException
     */
    public function getStorageFor(File $file): FileStorageInterface
    {
        return $this->storageIterator->getStorageFor($file);
    }

    /**
     * Lit le fichier et retourne son contenu.
     *
     * @throws FileNotFoundException
     */
    public function read(File $file): string
    {
        $storage = $this->getStorageFor($file);

        return $storage->read($file);
    }

    /**
     * Lit le fichier Image et retourne une URL.
     *
     * @throws FileNotFoundException
     */
    public function getImageUrl(File $file, string $size, bool $relativePath = false, bool $uncropped = false): string
    {
        $storage = $this->getStorageFor($file);

        return $storage->getImageUrl($file, $size, $relativePath, $uncropped);
    }

    /**
     * Prepare a File record with a PENDING status.
     * This record will hold all the data needed to create the file, except its content.
     *
     * @param Organization|Access|Person $owner       Owner of the file, either an organization, a person or both (access)
     * @param string                     $filename    The file's name (mandatory)
     * @param FileTypeEnum               $type        The type of the new file
     * @param Access                     $createdBy   Id of the access responsible for this creation
     * @param bool                       $isTemporary Is it a temporary file that can be deleted after some time
     * @param string|null                $mimeType    Mimetype of the file, if not provided, the method will try to guess it from its file name's extension
     * @param bool                       $flushFile   Should the newly created file be flushed after having been persisted?
     */
    public function prepareFile(
        Organization|Access|Person $owner,
        string $filename,
        FileTypeEnum $type,
        Access $createdBy,
        bool $isTemporary = false,
        FileVisibilityEnum $visibility = FileVisibilityEnum::NOBODY,
        ?string $mimeType = null,
        bool $flushFile = true,
    ): File {
        return $this
            ->localStorage
            ->prepareFile($owner, $filename, $type, $createdBy, $isTemporary, $visibility, $mimeType, $flushFile);
    }

    /**
     * Write the $content into the file storage and update the given File object's size, slug, status (READY)...
     *
     * @throws FileNotFoundException
     */
    public function write(File $file, string $content, Access $author): File
    {
        return $this
            ->localStorage
            ->write($file, $content, $author);
    }

    /**
     * Convenient method to successively prepare and write a file.
     */
    public function makeFile(
        Organization|Access|Person $owner,
        string $filename,
        FileTypeEnum $type,
        string $content,
        Access $author,
        bool $isTemporary = false,
        FileVisibilityEnum $visibility = FileVisibilityEnum::NOBODY,
        ?string $mimeType = null,
        ?string $config = null,
    ): File {
        return $this
            ->localStorage
            ->makeFile($owner, $filename, $type, $content, $author, $isTemporary, $visibility, $mimeType);
    }

    /**
     * Get the IRI to download this file (eg: /api/download/1).
     */
    public function getDownloadIri(File $file): string
    {
        return $this->iriConverter->getIriFromResource(
            File::class,
            UrlGeneratorInterfaceApiPlatform::ABS_PATH,
            new Get(),
            ['fileId' => $file->getId()]
        );
    }

    /**
     * Permanently delete the organization's files from each storage, and remove any reference
     * in the DB.
     */
    public function deleteOrganizationFiles(int $organizationId): void
    {
        foreach ($this->storageIterator->getStorages() as $storageService) {
            $storageService->deleteOrganizationFiles($organizationId);
        }

        $this->fileRepository->deleteByOrganization($organizationId);
    }

    /**
     * Permanently delete the person's files from each storage, and remove any reference
     * * in the DB.
     */
    public function deletePersonFiles(int $personId): void
    {
        foreach ($this->storageIterator->getStorages() as $storageService) {
            $storageService->deletePersonFiles($personId);
        }

        $this->fileRepository->deleteByPerson($personId);
    }
}
