package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.document.request.CreateDocumentRequest;
import dev.lions.unionflow.server.api.dto.document.request.CreatePieceJointeRequest;
import dev.lions.unionflow.server.api.dto.document.response.DocumentResponse;
import dev.lions.unionflow.server.api.dto.document.response.PieceJointeResponse;
import dev.lions.unionflow.server.repository.DocumentRepository;
import dev.lions.unionflow.server.resource.DocumentResource;
import dev.lions.unionflow.server.service.DocumentService;
import dev.lions.unionflow.server.service.FileStorageService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class DocumentResourceTest {

    @InjectMock
    DocumentService documentService;

    @InjectMock
    FileStorageService fileStorageService;

    @InjectMock
    DocumentRepository documentRepository;

    @Inject
    DocumentResource documentResource;

    // ============================================================
    // DOCUMENT - CRÉATION
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents retourne 201 avec données valides")
    void creerDocument_validRequest_returns201() {
        DocumentResponse created = new DocumentResponse();
        created.setId(UUID.randomUUID());
        created.setNomFichier("rapport-2025.pdf");
        created.setCheminStockage("/uploads/rapport-2025.pdf");

        when(documentService.creerDocument(any(CreateDocumentRequest.class))).thenReturn(created);

        Map<String, Object> body = Map.of(
                "nomFichier", "rapport-2025.pdf",
                "nomOriginal", "Rapport 2025.pdf",
                "cheminStockage", "/uploads/rapport-2025.pdf",
                "typeMime", "application/pdf",
                "tailleOctets", 204800
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents retourne 400 si erreur de service")
    void creerDocument_serviceThrows_returns400() {
        when(documentService.creerDocument(any(CreateDocumentRequest.class)))
                .thenThrow(new RuntimeException("Erreur de persistance"));

        Map<String, Object> body = Map.of(
                "nomFichier", "fichier-erreur.pdf",
                "cheminStockage", "/uploads/fichier-erreur.pdf",
                "tailleOctets", 1024
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // DOCUMENT - RECHERCHE PAR ID
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{id} retourne 200 quand trouvé")
    void trouverParId_found_returns200() {
        UUID id = UUID.randomUUID();
        DocumentResponse doc = new DocumentResponse();
        doc.setId(id);
        doc.setNomFichier("document.pdf");

        when(documentService.trouverParId(id)).thenReturn(doc);

        given()
                .pathParam("id", id)
                .when()
                .get("/api/documents/{id}")
                .then()
                .statusCode(200)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{id} retourne 404 quand non trouvé")
    void trouverParId_notFound_returns404() {
        UUID id = UUID.randomUUID();
        when(documentService.trouverParId(id))
                .thenThrow(new NotFoundException("Document non trouvé"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/documents/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{id} retourne 400 si erreur générale")
    void trouverParId_generalError_returns400() {
        UUID id = UUID.randomUUID();
        when(documentService.trouverParId(id))
                .thenThrow(new RuntimeException("Erreur DB"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/documents/{id}")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // DOCUMENT - TÉLÉCHARGEMENT
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/{id}/telechargement retourne 200 quand enregistré")
    void enregistrerTelechargement_found_returns200() {
        UUID id = UUID.randomUUID();
        doNothing().when(documentService).enregistrerTelechargement(id);

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .when()
                .post("/api/documents/{id}/telechargement")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/{id}/telechargement retourne 404 quand non trouvé")
    void enregistrerTelechargement_notFound_returns404() {
        UUID id = UUID.randomUUID();
        doThrow(new NotFoundException("Document non trouvé"))
                .when(documentService).enregistrerTelechargement(id);

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .when()
                .post("/api/documents/{id}/telechargement")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/{id}/telechargement retourne 400 si erreur générale")
    void enregistrerTelechargement_generalError_returns400() {
        UUID id = UUID.randomUUID();
        doThrow(new RuntimeException("Erreur DB"))
                .when(documentService).enregistrerTelechargement(id);

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .when()
                .post("/api/documents/{id}/telechargement")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // PIÈCES JOINTES
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 201 avec données valides")
    void creerPieceJointe_validRequest_returns201() {
        UUID documentId = UUID.randomUUID();
        UUID entiteId = UUID.randomUUID();

        PieceJointeResponse created = new PieceJointeResponse();
        created.setId(UUID.randomUUID());
        created.setDocumentId(documentId);
        created.setTypeEntiteRattachee("MEMBRE");
        created.setEntiteRattacheeId(entiteId);

        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class))).thenReturn(created);

        Map<String, Object> body = Map.of(
                "ordre", 1,
                "libelle", "Pièce d'identité",
                "documentId", documentId.toString(),
                "typeEntiteRattachee", "MEMBRE",
                "entiteRattacheeId", entiteId.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(201)
                .body("id", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 400 si champs obligatoires manquants")
    void creerPieceJointe_missingRequiredFields_returns400() {
        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class)))
                .thenThrow(new IllegalArgumentException("type_entite_rattachee et entite_rattachee_id sont obligatoires"));

        Map<String, Object> body = Map.of(
                "ordre", 1,
                "documentId", UUID.randomUUID().toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 400 si erreur générale")
    void creerPieceJointe_generalError_returns400() {
        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class)))
                .thenThrow(new RuntimeException("Erreur DB"));

        Map<String, Object> body = Map.of(
                "ordre", 1,
                "documentId", UUID.randomUUID().toString(),
                "typeEntiteRattachee", "ORGANISATION",
                "entiteRattacheeId", UUID.randomUUID().toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 201 avec tous les champs du CreatePieceJointeRequest (couvre L210-211)")
    void creerPieceJointe_tousChamps_returns201() {
        // Ce test s'assure que la ligne 210 (appel documentService.creerPieceJointe)
        // et 211 (Response.status(CREATED).entity(result)) sont bien exécutées.
        UUID documentId = UUID.randomUUID();
        UUID entiteId = UUID.randomUUID();

        PieceJointeResponse created = new PieceJointeResponse();
        created.setId(UUID.randomUUID());
        created.setDocumentId(documentId);
        created.setTypeEntiteRattachee("ORGANISATION");
        created.setEntiteRattacheeId(entiteId);
        created.setOrdre(2);
        created.setLibelle("Statuts de l'organisation");
        created.setCommentaire("Document fondateur");

        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class))).thenReturn(created);

        // Body complet avec tous les champs obligatoires pour passer la validation Bean
        Map<String, Object> body = Map.of(
                "ordre", 2,
                "libelle", "Statuts de l'organisation",
                "commentaire", "Document fondateur",
                "documentId", documentId.toString(),
                "typeEntiteRattachee", "ORGANISATION",
                "entiteRattacheeId", entiteId.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(201)
                .body("id", notNullValue())
                .body("typeEntiteRattachee", equalTo("ORGANISATION"));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 400 avec message d'erreur quand IllegalArgumentException (couvre L212-215 branche IllegalArgumentException)")
    void creerPieceJointe_illegalArgument_retourne400AvecMessage() {
        UUID documentId = UUID.randomUUID();
        UUID entiteId = UUID.randomUUID();

        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class)))
                .thenThrow(new IllegalArgumentException("Entité rattachée introuvable"));

        Map<String, Object> body = Map.of(
                "ordre", 1,
                "documentId", documentId.toString(),
                "typeEntiteRattachee", "COTISATION",
                "entiteRattacheeId", entiteId.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(400)
                .body("error", equalTo("Entité rattachée introuvable"));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/pieces-jointes retourne 400 avec message d'erreur quand exception générale (couvre L216-220 branche Exception)")
    void creerPieceJointe_exceptionGenerale_retourne400AvecMessage() {
        UUID documentId = UUID.randomUUID();
        UUID entiteId = UUID.randomUUID();

        when(documentService.creerPieceJointe(any(CreatePieceJointeRequest.class)))
                .thenThrow(new RuntimeException("Erreur interne de persistance"));

        Map<String, Object> body = Map.of(
                "ordre", 3,
                "documentId", documentId.toString(),
                "typeEntiteRattachee", "ADHESION",
                "entiteRattacheeId", entiteId.toString()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/documents/pieces-jointes")
                .then()
                .statusCode(400)
                .body("error", containsString("Erreur interne de persistance"));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{documentId}/pieces-jointes retourne 200 avec liste")
    void listerPiecesJointesParDocument_returns200() {
        UUID documentId = UUID.randomUUID();
        PieceJointeResponse pj = new PieceJointeResponse();
        pj.setId(UUID.randomUUID());
        pj.setDocumentId(documentId);
        pj.setTypeEntiteRattachee("MEMBRE");

        when(documentService.listerPiecesJointesParDocument(documentId)).thenReturn(List.of(pj));

        given()
                .pathParam("documentId", documentId)
                .when()
                .get("/api/documents/{documentId}/pieces-jointes")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{documentId}/pieces-jointes retourne 200 avec liste vide")
    void listerPiecesJointesParDocument_emptyList_returns200() {
        UUID documentId = UUID.randomUUID();
        when(documentService.listerPiecesJointesParDocument(documentId)).thenReturn(List.of());

        given()
                .pathParam("documentId", documentId)
                .when()
                .get("/api/documents/{documentId}/pieces-jointes")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /api/documents/{documentId}/pieces-jointes retourne 400 si erreur")
    void listerPiecesJointesParDocument_serviceThrows_returns400() {
        UUID documentId = UUID.randomUUID();
        when(documentService.listerPiecesJointesParDocument(documentId))
                .thenThrow(new RuntimeException("Erreur DB"));

        given()
                .pathParam("documentId", documentId)
                .when()
                .get("/api/documents/{documentId}/pieces-jointes")
                .then()
                .statusCode(400);
    }

    // ============================================================
    // UPLOAD DE FICHIER
    // ============================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload sans fichier retourne 400")
    void uploadFile_sansFile_returns400() {
        given()
                .contentType("multipart/form-data")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload avec fichier valide retourne 201 (couvre lignes 126-133)")
    void uploadFile_avecFichierValide_returns201() throws Exception {
        FileStorageService.FileMetadata metadata = FileStorageService.FileMetadata.builder()
                .nomFichier("uuid-filename.pdf")
                .nomOriginal("rapport.pdf")
                .cheminStockage("/uploads/uuid-filename.pdf")
                .typeMime("application/pdf")
                .tailleOctets(1024L)
                .hashMd5("d41d8cd98f00b204e9800998ecf8427e")
                .hashSha256("e3b0c44298fc1c149afbf4c8996fb924")
                .build();

        when(fileStorageService.storeFile(any(), anyString(), anyString(), anyLong()))
                .thenReturn(metadata);

        // Assigner un UUID lors du persist pour éviter NPE sur document.getId() (ligne 128)
        doAnswer(invocation -> {
            dev.lions.unionflow.server.entity.Document doc = invocation.getArgument(0);
            doc.setId(UUID.randomUUID());
            return null;
        }).when(documentRepository).persist(any(dev.lions.unionflow.server.entity.Document.class));

        given()
                .contentType("multipart/form-data")
                .multiPart("file", "rapport.pdf", "contenu pdf".getBytes(), "application/pdf")
                .multiPart("description", "Mon rapport")
                .multiPart("typeDocument", "PIECE_JUSTIFICATIVE")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(201);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload sans typeDocument (null) → PIECE_JUSTIFICATIVE par défaut (couvre L118 branche false)")
    void uploadFile_sansTypeDocument_defaultPieceJustificative_returns201() throws Exception {
        FileStorageService.FileMetadata metadata = FileStorageService.FileMetadata.builder()
                .nomFichier("uuid-sans-type.pdf")
                .nomOriginal("sans-type.pdf")
                .cheminStockage("/uploads/uuid-sans-type.pdf")
                .typeMime("application/pdf")
                .tailleOctets(512L)
                .hashMd5("abc123")
                .hashSha256("def456")
                .build();

        when(fileStorageService.storeFile(any(), anyString(), anyString(), anyLong()))
                .thenReturn(metadata);

        doAnswer(invocation -> {
            dev.lions.unionflow.server.entity.Document doc = invocation.getArgument(0);
            doc.setId(UUID.randomUUID());
            return null;
        }).when(documentRepository).persist(any(dev.lions.unionflow.server.entity.Document.class));

        // Pas de typeDocument → null → branche false → TypeDocument.PIECE_JUSTIFICATIVE
        given()
                .contentType("multipart/form-data")
                .multiPart("file", "sans-type.pdf", "contenu pdf".getBytes(), "application/pdf")
                .multiPart("description", "Document sans type explicite")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(201);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload fileStorageService lève IllegalArgumentException → 400")
    void uploadFile_storageIllegalArg_returns400() throws Exception {
        when(fileStorageService.storeFile(any(), anyString(), anyString(), anyLong()))
                .thenThrow(new IllegalArgumentException("Type MIME non autorisé"));

        given()
                .contentType("multipart/form-data")
                .multiPart("file", "malicious.exe", "contenu exe".getBytes(), "application/octet-stream")
                .multiPart("typeDocument", "PIECE_JUSTIFICATIVE")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload fileStorageService lève Exception → 500")
    void uploadFile_storageException_returns500() throws Exception {
        when(fileStorageService.storeFile(any(), anyString(), anyString(), anyLong()))
                .thenThrow(new RuntimeException("Erreur disque"));

        given()
                .contentType("multipart/form-data")
                .multiPart("file", "fichier.pdf", "contenu".getBytes(), "application/pdf")
                .multiPart("typeDocument", "PIECE_JUSTIFICATIVE")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload avec fichier ayant fileName vide → 400 (branche file.fileName()==null)")
    void uploadFile_fileParamPresentNoFilename_returns400OrError() {
        // Envoyer un multipart avec fileName="" → fileName() retourne "" (vide)
        // Le serveur ne lève pas de NPE sur l'entrée file != null mais fileName peut être vide.
        // En l'absence de fichier valide, le serveur retourne 400.
        given()
                .contentType("multipart/form-data")
                .multiPart("file", "", "".getBytes(), "application/octet-stream")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("POST /api/documents/upload sans part 'file' → file==null → 400 (branche L88 file==null)")
    void uploadFile_sansPartFile_returns400() {
        // Envoyer un multipart SANS la part "file" → FileUpload file = null → L88 true → 400
        given()
                .contentType("multipart/form-data")
                .multiPart("typeDocument", "PIECE_JUSTIFICATIVE")
                .when()
                .post("/api/documents/upload")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("uploadFile direct — file != null mais fileName() == null → 400 (L88 deuxième condition vraie)")
    void uploadFile_fileNotNullFileNameNull_l88SecondConditionTrue() throws Exception {
        // Mock FileUpload avec fileName() retournant null → L88: file!=null MAIS file.fileName()==null → true → 400
        org.jboss.resteasy.reactive.multipart.FileUpload mockFile =
                org.mockito.Mockito.mock(org.jboss.resteasy.reactive.multipart.FileUpload.class);
        org.mockito.Mockito.when(mockFile.fileName()).thenReturn(null);

        jakarta.ws.rs.core.Response response = documentResource.uploadFile(mockFile, null, null);

        Assertions.assertThat(response.getStatus()).isEqualTo(400);
    }
}
