package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;

/**
 * Tests unitaires pour FileStorageService.
 *
 * <p>Couvre storeFile() avec ses branches de validation (taille, type MIME),
 * le calcul des hash MD5/SHA-256 et la construction de FileMetadata.
 *
 * @author UnionFlow Team
 */
@QuarkusTest
class FileStorageServiceTest {

    @Inject
    FileStorageService fileStorageService;

    // =========================================================================
    // Helpers
    // =========================================================================

    private byte[] smallPdfContent() {
        // Minimal PDF-like content pour les tests
        return "%PDF-1.4 test content".getBytes();
    }

    private InputStream toStream(byte[] data) {
        return new ByteArrayInputStream(data);
    }

    // =========================================================================
    // Validation — taille
    // =========================================================================

    @Test
    @DisplayName("storeFile - taille dépassée → IllegalArgumentException")
    void storeFile_fileTooLarge_throws() {
        long overSize = 5L * 1024 * 1024 + 1; // 5 MB + 1 octet

        assertThatThrownBy(() -> fileStorageService.storeFile(
                toStream(new byte[0]), "test.pdf", "application/pdf", overSize))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("volumineux");
    }

    @Test
    @DisplayName("storeFile - taille exactement à la limite (5 MB) → autorisé")
    void storeFile_fileSizeExactLimit_allowed(@TempDir Path tempDir) throws Exception {
        // On injecte le répertoire temporaire via le @ConfigProperty
        // (le service utilise './uploads' par défaut, on passe juste un stream de 1 byte)
        byte[] data = smallPdfContent();
        long exactLimit = 5L * 1024 * 1024;

        // Ne doit pas lever IllegalArgumentException pour la taille
        // (peut lever IOException si path non accessible — on teste juste la validation)
        try {
            FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                    toStream(data), "test.pdf", "application/pdf", exactLimit);
            // Si succès → assertions sur la metadata
            assertThat(meta).isNotNull();
        } catch (IOException e) {
            // IOException d'accès au répertoire est acceptable en test — pas IllegalArgumentException
            assertThat(e).isNotInstanceOf(IllegalArgumentException.class);
        }
    }

    // =========================================================================
    // Validation — type MIME
    // =========================================================================

    @Test
    @DisplayName("storeFile - type MIME non autorisé → IllegalArgumentException")
    void storeFile_mimeTypeNotAllowed_throws() {
        assertThatThrownBy(() -> fileStorageService.storeFile(
                toStream(new byte[]{1, 2, 3}), "virus.exe", "application/octet-stream", 100L))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("non autorisé");
    }

    @Test
    @DisplayName("storeFile - type MIME null → IllegalArgumentException")
    void storeFile_mimeTypeNull_throws() {
        assertThatThrownBy(() -> fileStorageService.storeFile(
                toStream(new byte[]{1, 2}), "file.pdf", null, 100L))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("non autorisé");
    }

    @Test
    @DisplayName("storeFile - type MIME vide → IllegalArgumentException")
    void storeFile_mimeTypeEmpty_throws() {
        assertThatThrownBy(() -> fileStorageService.storeFile(
                toStream(new byte[]{1, 2}), "file.pdf", "", 100L))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("non autorisé");
    }

    // =========================================================================
    // Chaque type MIME autorisé
    // =========================================================================

    @Test
    @DisplayName("storeFile - image/jpeg → autorisé (pas d'erreur de validation)")
    void storeFile_mimeTypeJpeg_noValidationError(@TempDir Path tempDir) {
        byte[] data = "fake jpeg".getBytes();
        assertMimeTypeAccepted(data, "photo.jpg", "image/jpeg");
    }

    @Test
    @DisplayName("storeFile - image/jpg → autorisé")
    void storeFile_mimeTypeJpg_noValidationError() {
        assertMimeTypeAccepted("test".getBytes(), "photo.jpg", "image/jpg");
    }

    @Test
    @DisplayName("storeFile - image/png → autorisé")
    void storeFile_mimeTypePng_noValidationError() {
        assertMimeTypeAccepted("png".getBytes(), "image.png", "image/png");
    }

    @Test
    @DisplayName("storeFile - image/gif → autorisé")
    void storeFile_mimeTypeGif_noValidationError() {
        assertMimeTypeAccepted("gif".getBytes(), "anim.gif", "image/gif");
    }

    @Test
    @DisplayName("storeFile - application/pdf → autorisé")
    void storeFile_mimeTypePdf_noValidationError() {
        assertMimeTypeAccepted(smallPdfContent(), "doc.pdf", "application/pdf");
    }

    @Test
    @DisplayName("storeFile - type MIME insensible à la casse → autorisé")
    void storeFile_mimeTypeCaseInsensitive_noValidationError() {
        // "IMAGE/JPEG" doit fonctionner (equalsIgnoreCase)
        assertMimeTypeAccepted("jpeg".getBytes(), "img.jpg", "IMAGE/JPEG");
    }

    // =========================================================================
    // Happy path — résultat FileMetadata
    // =========================================================================

    @Test
    @DisplayName("storeFile - happy path pdf → FileMetadata correcte")
    void storeFile_happyPath_returnsCorrectMetadata() throws Exception {
        byte[] content = "Hello UnionFlow PDF Content".getBytes();
        String filename = "document.pdf";
        String mimeType = "application/pdf";
        long fileSize = content.length;

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), filename, mimeType, fileSize);

        assertThat(meta).isNotNull();
        assertThat(meta.getNomOriginal()).isEqualTo(filename);
        assertThat(meta.getTypeMime()).isEqualTo(mimeType);
        assertThat(meta.getTailleOctets()).isEqualTo(fileSize);
        assertThat(meta.getNomFichier()).isNotBlank();
        assertThat(meta.getNomFichier()).endsWith(".pdf");
        assertThat(meta.getCheminStockage()).isNotBlank();
        assertThat(meta.getHashMd5()).isNotBlank();
        assertThat(meta.getHashSha256()).isNotBlank();
    }

    @Test
    @DisplayName("storeFile - fichier sans extension → nom unique généré sans extension")
    void storeFile_filenameWithoutExtension_noExtension() throws Exception {
        byte[] content = "data without extension".getBytes();

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "noextension", "image/jpeg", content.length);

        assertThat(meta.getNomFichier()).doesNotContain(".");
        assertThat(meta.getNomOriginal()).isEqualTo("noextension");
    }

    @Test
    @DisplayName("storeFile - fichier image PNG → extension .png dans le nom stocké")
    void storeFile_pngFile_extensionPreserved() throws Exception {
        byte[] content = "fake png bytes".getBytes();

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "screenshot.png", "image/png", content.length);

        assertThat(meta.getNomFichier()).endsWith(".png");
    }

    @Test
    @DisplayName("storeFile - chemin relatif contient date YYYY/MM/DD")
    void storeFile_cheminContientDate() throws Exception {
        byte[] content = "test content".getBytes();

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "test.pdf", "application/pdf", content.length);

        // Le chemin doit avoir le format YYYY/MM/DD/uuid.pdf
        assertThat(meta.getCheminStockage()).matches("\\d{4}/\\d{2}/\\d{2}/.*");
    }

    @Test
    @DisplayName("storeFile - hash MD5 a 32 caractères hexadécimaux")
    void storeFile_hashMd5Format() throws Exception {
        byte[] content = "hashable content".getBytes();

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "file.pdf", "application/pdf", content.length);

        assertThat(meta.getHashMd5()).hasSize(32);
        assertThat(meta.getHashMd5()).matches("[0-9a-f]+");
    }

    @Test
    @DisplayName("storeFile - hash SHA-256 a 64 caractères hexadécimaux")
    void storeFile_hashSha256Format() throws Exception {
        byte[] content = "sha256 content".getBytes();

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "file.jpg", "image/jpeg", content.length);

        assertThat(meta.getHashSha256()).hasSize(64);
        assertThat(meta.getHashSha256()).matches("[0-9a-f]+");
    }

    @Test
    @DisplayName("storeFile - contenu vide (0 octets) → FileMetadata créée")
    void storeFile_emptyContent_metadataCreated() throws Exception {
        byte[] empty = new byte[0];

        FileStorageService.FileMetadata meta = fileStorageService.storeFile(
                new ByteArrayInputStream(empty), "empty.pdf", "application/pdf", 0L);

        assertThat(meta).isNotNull();
        assertThat(meta.getTailleOctets()).isEqualTo(0L);
        assertThat(meta.getHashMd5()).isNotBlank();
        assertThat(meta.getHashSha256()).isNotBlank();
    }

    @Test
    @DisplayName("storeFile - deux appels successifs → noms de fichiers uniques (UUID)")
    void storeFile_twoCallsProduceUniqueNames() throws Exception {
        byte[] content = "same content".getBytes();

        FileStorageService.FileMetadata meta1 = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "doc.pdf", "application/pdf", content.length);
        FileStorageService.FileMetadata meta2 = fileStorageService.storeFile(
                new ByteArrayInputStream(content), "doc.pdf", "application/pdf", content.length);

        assertThat(meta1.getNomFichier()).isNotEqualTo(meta2.getNomFichier());
    }

    // =========================================================================
    // Private helper
    // =========================================================================

    /** Vérifie qu'un type MIME donné ne déclenche pas d'IllegalArgumentException. */
    private void assertMimeTypeAccepted(byte[] data, String filename, String mimeType) {
        try {
            fileStorageService.storeFile(new ByteArrayInputStream(data), filename, mimeType, data.length);
            // OK — soit succès soit IOException (pas IllegalArgumentException)
        } catch (IllegalArgumentException e) {
            throw new AssertionError("MIME type " + mimeType + " devrait être autorisé", e);
        } catch (Exception e) {
            // IOException / NoSuchAlgorithmException d'accès fichier : pas un problème de validation → OK
        }
    }
}
