<?php

declare(strict_types=1);

namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Integration\Domain\Exception\InvalidPathException;
use App\Module\Integration\Domain\Exception\ShareConnectionException;
use App\Module\Integration\Domain\Exception\ShareNotConfiguredException;
use App\Module\Integration\Domain\Service\FileEntry;
use App\Module\Integration\Domain\Service\FileSource;
use App\Module\Integration\Domain\Service\SharePathResolver;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskDocument;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Uid\Uuid;

use function in_array;

/**
 * @implements ProcessorInterface<TaskDocument, TaskDocument>
 */
final readonly class TaskDocumentProcessor implements ProcessorInterface
{
    private const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB

    private const ALLOWED_MIME_TYPES = [
        'image/jpeg', 'image/png', 'image/gif', 'image/webp',
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'application/vnd.ms-powerpoint',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'text/plain', 'text/csv',
        'application/zip', 'application/x-rar-compressed', 'application/gzip',
        'application/json', 'application/xml', 'text/xml',
    ];

    private const MIME_TO_EXTENSION = [
        'image/jpeg'                                                                => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif',
        'image/webp'                                                                => 'webp',
        'application/pdf'                                                           => 'pdf',
        'application/msword'                                                        => 'doc',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'   => 'docx',
        'application/vnd.ms-excel'                                                  => 'xls',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'         => 'xlsx',
        'application/vnd.ms-powerpoint'                                             => 'ppt',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
        'text/plain'                                                                => 'txt', 'text/csv' => 'csv',
        'application/zip'                                                           => 'zip', 'application/x-rar-compressed' => 'rar', 'application/gzip' => 'gz',
        'application/json'                                                          => 'json', 'application/xml' => 'xml', 'text/xml' => 'xml',
    ];

    public function __construct(
        private EntityManagerInterface $entityManager,
        private Security $security,
        private RequestStack $requestStack,
        private FileSource $fileSource,
        private SharePathResolver $pathResolver,
        private string $uploadDir,
    ) {}

    /**
     * @param TaskDocument $data
     */
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): TaskDocument
    {
        // Défense en profondeur : l'opération Post est déjà protégée par ROLE_ADMIN, mais on
        // re-vérifie ici pour que les deux chemins (upload ET lien partage) restent sûrs si la
        // configuration de sécurité de l'opération venait à changer.
        if (!$this->security->isGranted('ROLE_ADMIN')) {
            throw new AccessDeniedHttpException('Creating task documents requires admin privileges.');
        }

        $request = $this->requestStack->getCurrentRequest();

        if (null === $request) {
            throw new BadRequestHttpException('No request available.');
        }

        // Deux modes de création : upload d'un fichier (multipart) ou lien vers un fichier du partage SMB (JSON).
        $sharePath = $this->extractSharePath($request);

        $document = null !== $sharePath
            ? $this->createShareLink($request, $sharePath)
            : $this->createUpload($request);

        $document->setCreatedAt(new DateTimeImmutable());
        $document->setUploadedBy($this->security->getUser());

        $this->entityManager->persist($document);
        $this->entityManager->flush();

        return $document;
    }

    private function extractSharePath(Request $request): ?string
    {
        // Lien SMB : champ multipart/form OU corps JSON { "sharePath": "..." }
        $fromForm = $request->request->get('sharePath');

        if (is_string($fromForm) && '' !== $fromForm) {
            return $fromForm;
        }

        if (str_contains((string) $request->headers->get('Content-Type'), 'application/json')) {
            $payload = json_decode($request->getContent() ?: '{}', true);

            if (is_array($payload) && isset($payload['sharePath']) && is_string($payload['sharePath']) && '' !== $payload['sharePath']) {
                return $payload['sharePath'];
            }
        }

        return null;
    }

    private function createUpload(Request $request): TaskDocument
    {
        $file = $request->files->get('file');

        if (null === $file || !$file->isValid()) {
            throw new BadRequestHttpException('No valid file uploaded.');
        }

        if ($file->getSize() > self::MAX_FILE_SIZE) {
            throw new BadRequestHttpException('File size exceeds 50 MB limit.');
        }

        $task = $this->resolveTask($request->request->get('task', ''));

        // Use server-detected MIME type (finfo), not the client-supplied one
        $originalName = $file->getClientOriginalName();
        $mimeType     = $file->getMimeType() ?: 'application/octet-stream';
        $fileSize     = $file->getSize();

        if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) {
            throw new BadRequestHttpException(sprintf('File type "%s" is not allowed.', $mimeType));
        }

        $extension = self::MIME_TO_EXTENSION[$mimeType] ?? 'bin';
        $fileName  = Uuid::v4()->toRfc4122().'.'.$extension;

        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0o775, true);
        }

        $file->move($this->uploadDir, $fileName);

        $document = new TaskDocument();
        $document->setTask($task);
        $document->setOriginalName($originalName);
        $document->setFileName($fileName);
        $document->setMimeType($mimeType);
        $document->setSize($fileSize);

        return $document;
    }

    private function createShareLink(Request $request, string $rawSharePath): TaskDocument
    {
        $taskIri = $request->request->get('task');

        if (!is_string($taskIri) || '' === $taskIri) {
            $payload = json_decode($request->getContent() ?: '{}', true);
            $taskIri = is_array($payload) ? ($payload['task'] ?? '') : '';
        }

        $task = $this->resolveTask((string) $taskIri);

        try {
            $path = $this->pathResolver->normalizeRelative($rawSharePath);
        } catch (InvalidPathException) {
            throw new BadRequestHttpException('Invalid share path.');
        }

        if ('' === $path) {
            throw new BadRequestHttpException('A share path is required.');
        }

        $entry = $this->findShareEntry($path);

        if (null === $entry) {
            throw new BadRequestHttpException('File not found on the share.');
        }

        if (!in_array($entry->mimeType, self::ALLOWED_MIME_TYPES, true)) {
            throw new BadRequestHttpException(sprintf('File type "%s" is not allowed.', $entry->mimeType));
        }

        $document = new TaskDocument();
        $document->setTask($task);
        $document->setOriginalName($entry->name);
        $document->setSharePath($path);
        $document->setMimeType($entry->mimeType);
        $document->setSize($entry->size);

        return $document;
    }

    /**
     * Récupère les métadonnées (taille, type) du fichier sur le partage en listant son dossier parent.
     */
    private function findShareEntry(string $path): ?FileEntry
    {
        $parent = dirname($path);
        $parent = ('.' === $parent || '/' === $parent) ? '' : $parent;
        $name   = basename($path);

        try {
            $entries = $this->fileSource->dir($parent);
        } catch (ShareNotConfiguredException) {
            throw new BadRequestHttpException('Share not configured.');
        } catch (ShareConnectionException) {
            throw new BadRequestHttpException('Unable to reach the share.');
        }

        foreach ($entries as $entry) {
            if (!$entry->isDir && $entry->name === $name) {
                return $entry;
            }
        }

        return null;
    }

    private function resolveTask(string $taskIri): Task
    {
        if ('' === $taskIri) {
            throw new BadRequestHttpException('A task IRI is required.');
        }

        $task = $this->entityManager->getRepository(Task::class)->find((int) basename($taskIri));

        if (null === $task) {
            throw new BadRequestHttpException('Task not found.');
        }

        return $task;
    }
}
