<?php

namespace App\Controller;

use App\Entity\ScanAttachment;
use App\Entity\User;
use App\Repository\ScanAttachmentRepository;
use App\Repository\ScanSessionRepository;
use App\Security\Voter\ScanVoter;
use App\Service\AttachmentStorage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

/**
 * Documents libres joints à un inventaire ("Autres documents") + commentaire associé.
 * Accès gated par ScanVoter::VIEW (= visibilité du scan dans le périmètre).
 */
#[IsGranted('ROLE_USER')]
final class AttachmentController extends AbstractController
{
    #[Route('/inventaires/{id}/documents', name: 'app_attachment_upload', methods: ['POST'], requirements: ['id' => '[0-9a-f-]{36}'])]
    public function upload(
        string $id,
        Request $request,
        ScanSessionRepository $sessions,
        AttachmentStorage $storage,
        EntityManagerInterface $em,
    ): Response {
        $session = $this->loadScan($id, $sessions);
        $this->denyAccessUnlessGranted(ScanVoter::EDIT_DOCUMENTS, $session);

        /** @var User $user */
        $user = $this->getUser();

        // Commentaire associé aux fichiers de cette soumission (1 fichier = 1 commentaire).
        $comment = $request->request->get('comment');
        $comment = \is_string($comment) ? $comment : null;

        // Fichiers (input multiple name="files[]").
        $files = $request->files->get('files', []);
        $files = \is_array($files) ? $files : [$files];
        $added = 0;
        $errors = [];
        foreach ($files as $file) {
            if (!$file instanceof UploadedFile) {
                continue;
            }
            if ($file->getSize() > AttachmentStorage::MAX_SIZE) {
                $errors[] = sprintf('%s : fichier trop volumineux (max 20 Mo).', $file->getClientOriginalName());
                continue;
            }
            try {
                $original = $file->getClientOriginalName();
                // MIME détecté côté serveur (contenu réel), pas celui annoncé par le client (falsifiable).
                $mime = $file->getMimeType() ?: $file->getClientMimeType() ?: 'application/octet-stream';
                $size = (int) $file->getSize();
                $storedName = $storage->store($id, $file); // déplace le fichier (à faire avant de lire le path)
                $attachment = new ScanAttachment($session, $storedName, $original, $mime, $size, $user->getUserIdentifier());
                $attachment->setComment($comment);
                $em->persist($attachment);
                ++$added;
            } catch (\Throwable $e) {
                $errors[] = $e->getMessage();
            }
        }

        $em->flush();

        if ($added > 0) {
            return $this->json([
                'success' => true,
                'message' => sprintf('%d document(s) ajouté(s).', $added) . ([] !== $errors ? ' ' . implode(' ', $errors) : ''),
            ]);
        }

        return $this->json([
            'error' => [] === $errors ? 'Veuillez sélectionner au moins un fichier.' : implode(' ', $errors),
        ], Response::HTTP_BAD_REQUEST);
    }

    #[Route('/inventaires/{id}/documents/{attId}/commentaire', name: 'app_attachment_comment', methods: ['POST'], requirements: ['id' => '[0-9a-f-]{36}', 'attId' => '\d+'])]
    public function editComment(
        string $id,
        int $attId,
        Request $request,
        ScanSessionRepository $sessions,
        ScanAttachmentRepository $attachments,
        EntityManagerInterface $em,
    ): Response {
        $session = $this->loadScan($id, $sessions);
        $this->denyAccessUnlessGranted(ScanVoter::EDIT_DOCUMENTS, $session);

        $attachment = $attachments->find($attId);
        if (null === $attachment || $attachment->getScan()->getId() !== $session->getId()) {
            return $this->json(['error' => 'Document introuvable.'], Response::HTTP_NOT_FOUND);
        }

        $comment = $request->request->get('comment');
        $attachment->setComment(\is_string($comment) ? $comment : null);
        $em->flush();

        return $this->json(['success' => true, 'message' => 'Commentaire enregistré.']);
    }

    #[Route('/inventaires/{id}/documents/{attId}', name: 'app_attachment_download', methods: ['GET'], requirements: ['id' => '[0-9a-f-]{36}', 'attId' => '\d+'])]
    public function download(
        string $id,
        int $attId,
        ScanSessionRepository $sessions,
        ScanAttachmentRepository $attachments,
        AttachmentStorage $storage,
    ): Response {
        $session = $this->loadScan($id, $sessions);
        $attachment = $attachments->find($attId);
        if (null === $attachment || $attachment->getScan()->getId() !== $session->getId()) {
            throw $this->createNotFoundException();
        }

        $path = $storage->pathFor($id, $attachment->getStoredName());
        if (null === $path) {
            throw $this->createNotFoundException();
        }

        // On ne sert en ligne que les images whitelistées ; tout le reste en octet-stream + nosniff,
        // et toujours en pièce jointe (jamais rendu inline) → pas d'exécution dans le navigateur.
        $imageMimes = ['image/jpeg', 'image/png', 'image/webp'];
        $mime = $attachment->getMimeType();
        $contentType = \in_array($mime, $imageMimes, true) ? $mime : 'application/octet-stream';

        $response = new BinaryFileResponse($path);
        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $attachment->getOriginalName());
        $response->headers->set('Content-Type', $contentType);
        $response->headers->set('X-Content-Type-Options', 'nosniff');

        return $response;
    }

    #[Route('/inventaires/{id}/documents/{attId}/supprimer', name: 'app_attachment_delete', methods: ['POST'], requirements: ['id' => '[0-9a-f-]{36}', 'attId' => '\d+'])]
    public function delete(
        string $id,
        int $attId,
        ScanSessionRepository $sessions,
        ScanAttachmentRepository $attachments,
        AttachmentStorage $storage,
        EntityManagerInterface $em,
    ): Response {
        $session = $this->loadScan($id, $sessions);
        $this->denyAccessUnlessGranted(ScanVoter::EDIT_DOCUMENTS, $session);

        $attachment = $attachments->find($attId);
        if (null !== $attachment && $attachment->getScan()->getId() === $session->getId()) {
            $storage->delete($id, $attachment->getStoredName());
            $em->remove($attachment);
            $em->flush();
        }

        return $this->json(['success' => true, 'message' => 'Document supprimé.']);
    }

    private function loadScan(string $id, ScanSessionRepository $sessions): \App\Entity\ScanSession
    {
        /** @var User $user */
        $user = $this->getUser();
        $session = $sessions->findOneInScope($id, $user->scopeConcessions());
        if (null === $session) {
            throw $this->createNotFoundException();
        }
        $this->denyAccessUnlessGranted(ScanVoter::VIEW, $session);

        return $session;
    }
}
