<?php

namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Stockage privé des documents joints aux inventaires ("Autres documents"), hors DocumentRoot.
 * Path : {baseDir}/{sessionId}/{storedName}, avec storedName = {uuid}.{ext}.
 * Le sessionId est un UUID (regex stricte) → pas de path traversal.
 */
final class AttachmentStorage
{
    private const SESSION_PATTERN = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/';
    private const STORED_PATTERN = '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z0-9]{1,8}$/';

    /** Extensions acceptées. */
    public const ALLOWED_EXTENSIONS = ['pdf', 'jpg', 'jpeg', 'png', 'webp', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt'];

    /** Taille max par fichier (20 Mo). */
    public const MAX_SIZE = 20 * 1024 * 1024;

    public function __construct(
        #[Autowire('%kernel.project_dir%/var/uploads/attachments')]
        private readonly string $baseDir,
        private readonly Filesystem $fs = new Filesystem(),
    ) {
    }

    private static function isValidSessionId(string $id): bool
    {
        return 1 === preg_match(self::SESSION_PATTERN, $id);
    }

    /**
     * Enregistre un fichier uploadé et retourne son nom de stockage ({uuid}.{ext}).
     */
    public function store(string $sessionId, UploadedFile $file): string
    {
        if (!self::isValidSessionId($sessionId)) {
            throw new \InvalidArgumentException('Identifiant de session invalide.');
        }

        $ext = strtolower($file->getClientOriginalExtension() ?: $file->guessExtension() ?: 'bin');
        if (!\in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
            throw new \InvalidArgumentException(sprintf('Type de fichier non autorisé : .%s', $ext));
        }

        $storedName = $this->uuid() . '.' . $ext;
        $dir = $this->sessionDir($sessionId);
        $this->fs->mkdir($dir, 0750);
        $file->move($dir, $storedName);

        return $storedName;
    }

    /** Chemin absolu d'un document, ou null si introuvable / identifiants invalides. */
    public function pathFor(string $sessionId, string $storedName): ?string
    {
        if (!self::isValidSessionId($sessionId) || 1 !== preg_match(self::STORED_PATTERN, $storedName)) {
            return null;
        }
        $path = $this->sessionDir($sessionId) . '/' . $storedName;

        return is_file($path) ? $path : null;
    }

    public function delete(string $sessionId, string $storedName): void
    {
        $path = $this->pathFor($sessionId, $storedName);
        if (null !== $path) {
            $this->fs->remove($path);
        }
    }

    /** Supprime tout le dossier de documents d'un scan. */
    public function deleteForScan(string $sessionId): void
    {
        if (!self::isValidSessionId($sessionId)) {
            return;
        }
        $dir = $this->sessionDir($sessionId);
        if (is_dir($dir)) {
            $this->fs->remove($dir);
        }
    }

    private function sessionDir(string $sessionId): string
    {
        return $this->baseDir . '/' . $sessionId;
    }

    /** UUID v4 sans dépendance externe. */
    private function uuid(): string
    {
        $data = random_bytes(16);
        $data[6] = \chr(\ord($data[6]) & 0x0F | 0x40);
        $data[8] = \chr(\ord($data[8]) & 0x3F | 0x80);

        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}
