package dev.lions.unionflow.server.resource;

import dev.lions.unionflow.server.api.dto.solidarite.aide.AideDTO;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.service.AideService;
import io.quarkus.test.junit.QuarkusTest;
import org.mockito.Mock;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;

/**
 * Tests d'intégration pour AideResource
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@QuarkusTest
@DisplayName("AideResource - Tests d'intégration")
class AideResourceTest {

    @Mock
    AideService aideService;

    private AideDTO aideDTOTest;
    private List<AideDTO> listeAidesTest;

    @BeforeEach
    void setUp() {
        // DTO de test
        aideDTOTest = new AideDTO();
        aideDTOTest.setId(UUID.randomUUID());
        aideDTOTest.setNumeroReference("AIDE-2025-TEST01");
        aideDTOTest.setTitre("Aide médicale urgente");
        aideDTOTest.setDescription("Demande d'aide pour frais médicaux urgents");
        aideDTOTest.setTypeAide("MEDICALE");
        aideDTOTest.setMontantDemande(new BigDecimal("500000.00"));
        aideDTOTest.setStatut("EN_ATTENTE");
        aideDTOTest.setPriorite("URGENTE");
        aideDTOTest.setMembreDemandeurId(UUID.randomUUID());
        aideDTOTest.setAssociationId(UUID.randomUUID());
        aideDTOTest.setActif(true);

        // Liste de test
        listeAidesTest = Arrays.asList(aideDTOTest);
    }

    @Nested
    @DisplayName("Tests des endpoints CRUD")
    class CrudEndpointsTests {

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("GET /api/aides - Liste des aides")
        void testListerAides() {
            // Given
            when(aideService.listerAidesActives(0, 20)).thenReturn(listeAidesTest);

            // When & Then
            given()
                .when()
                    .get("/api/aides")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("size()", is(1))
                    .body("[0].titre", equalTo("Aide médicale urgente"))
                    .body("[0].statut", equalTo("EN_ATTENTE"));
        }

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("GET /api/aides/{id} - Récupération par ID")
        void testObtenirAideParId() {
            // Given
            when(aideService.obtenirAideParId(1L)).thenReturn(aideDTOTest);

            // When & Then
            given()
                .when()
                    .get("/api/aides/1")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("titre", equalTo("Aide médicale urgente"))
                    .body("numeroReference", equalTo("AIDE-2025-TEST01"));
        }

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("GET /api/aides/{id} - Aide non trouvée")
        void testObtenirAideParId_NonTrouvee() {
            // Given
            when(aideService.obtenirAideParId(999L)).thenThrow(new NotFoundException("Demande d'aide non trouvée"));

            // When & Then
            given()
                .when()
                    .get("/api/aides/999")
                .then()
                    .statusCode(404)
                    .contentType(ContentType.JSON)
                    .body("error", equalTo("Demande d'aide non trouvée"));
        }

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("POST /api/aides - Création d'aide")
        void testCreerAide() {
            // Given
            when(aideService.creerAide(any(AideDTO.class))).thenReturn(aideDTOTest);

            // When & Then
            given()
                .contentType(ContentType.JSON)
                .body(aideDTOTest)
                .when()
                    .post("/api/aides")
                .then()
                    .statusCode(201)
                    .contentType(ContentType.JSON)
                    .body("titre", equalTo("Aide médicale urgente"))
                    .body("numeroReference", equalTo("AIDE-2025-TEST01"));
        }

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("PUT /api/aides/{id} - Mise à jour d'aide")
        void testMettreAJourAide() {
            // Given
            AideDTO aideMiseAJour = new AideDTO();
            aideMiseAJour.setTitre("Titre modifié");
            aideMiseAJour.setDescription("Description modifiée");
            
            when(aideService.mettreAJourAide(eq(1L), any(AideDTO.class))).thenReturn(aideMiseAJour);

            // When & Then
            given()
                .contentType(ContentType.JSON)
                .body(aideMiseAJour)
                .when()
                    .put("/api/aides/1")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("titre", equalTo("Titre modifié"));
        }
    }

    @Nested
    @DisplayName("Tests des endpoints métier")
    class EndpointsMetierTests {

        @Test
        @TestSecurity(user = "evaluateur", roles = {"evaluateur_aide"})
        @DisplayName("POST /api/aides/{id}/approuver - Approbation d'aide")
        void testApprouverAide() {
            // Given
            AideDTO aideApprouvee = new AideDTO();
            aideApprouvee.setStatut("APPROUVEE");
            aideApprouvee.setMontantApprouve(new BigDecimal("400000.00"));
            
            when(aideService.approuverAide(eq(1L), any(BigDecimal.class), anyString()))
                    .thenReturn(aideApprouvee);

            Map<String, Object> approbationData = Map.of(
                "montantApprouve", "400000.00",
                "commentaires", "Aide approuvée après évaluation"
            );

            // When & Then
            given()
                .contentType(ContentType.JSON)
                .body(approbationData)
                .when()
                    .post("/api/aides/1/approuver")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("statut", equalTo("APPROUVEE"));
        }

        @Test
        @TestSecurity(user = "evaluateur", roles = {"evaluateur_aide"})
        @DisplayName("POST /api/aides/{id}/rejeter - Rejet d'aide")
        void testRejeterAide() {
            // Given
            AideDTO aideRejetee = new AideDTO();
            aideRejetee.setStatut("REJETEE");
            aideRejetee.setRaisonRejet("Dossier incomplet");
            
            when(aideService.rejeterAide(eq(1L), anyString())).thenReturn(aideRejetee);

            Map<String, String> rejetData = Map.of(
                "raisonRejet", "Dossier incomplet"
            );

            // When & Then
            given()
                .contentType(ContentType.JSON)
                .body(rejetData)
                .when()
                    .post("/api/aides/1/rejeter")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("statut", equalTo("REJETEE"));
        }

        @Test
        @TestSecurity(user = "tresorier", roles = {"tresorier"})
        @DisplayName("POST /api/aides/{id}/verser - Versement d'aide")
        void testMarquerCommeVersee() {
            // Given
            AideDTO aideVersee = new AideDTO();
            aideVersee.setStatut("VERSEE");
            aideVersee.setMontantVerse(new BigDecimal("400000.00"));
            
            when(aideService.marquerCommeVersee(eq(1L), any(BigDecimal.class), anyString(), anyString()))
                    .thenReturn(aideVersee);

            Map<String, Object> versementData = Map.of(
                "montantVerse", "400000.00",
                "modeVersement", "MOBILE_MONEY",
                "numeroTransaction", "TXN123456789"
            );

            // When & Then
            given()
                .contentType(ContentType.JSON)
                .body(versementData)
                .when()
                    .post("/api/aides/1/verser")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("statut", equalTo("VERSEE"));
        }
    }

    @Nested
    @DisplayName("Tests des endpoints de recherche")
    class EndpointsRechercheTests {

        @Test
        @TestSecurity(user = "membre", roles = {"membre"})
        @DisplayName("GET /api/aides/statut/{statut} - Filtrage par statut")
        void testListerAidesParStatut() {
            // Given
            when(aideService.listerAidesParStatut(StatutAide.EN_ATTENTE, 0, 20))
                    .thenReturn(listeAidesTest);

            // When & Then
            given()
                .when()
                    .get("/api/aides/statut/EN_ATTENTE")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("size()", is(1))
                    .body("[0].statut", equalTo("EN_ATTENTE"));
        }

        @Test
        @TestSecurity(user = "membre", roles = {"membre"})
        @DisplayName("GET /api/aides/membre/{membreId} - Aides d'un membre")
        void testListerAidesParMembre() {
            // Given
            when(aideService.listerAidesParMembre(1L, 0, 20)).thenReturn(listeAidesTest);

            // When & Then
            given()
                .when()
                    .get("/api/aides/membre/1")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("size()", is(1));
        }

        @Test
        @TestSecurity(user = "membre", roles = {"membre"})
        @DisplayName("GET /api/aides/recherche - Recherche textuelle")
        void testRechercherAides() {
            // Given
            when(aideService.rechercherAides("médical", 0, 20)).thenReturn(listeAidesTest);

            // When & Then
            given()
                .queryParam("q", "médical")
                .when()
                    .get("/api/aides/recherche")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("size()", is(1));
        }

        @Test
        @TestSecurity(user = "admin", roles = {"admin"})
        @DisplayName("GET /api/aides/statistiques - Statistiques")
        void testObtenirStatistiques() {
            // Given
            Map<String, Object> statistiques = Map.of(
                "total", 100L,
                "enAttente", 25L,
                "approuvees", 50L,
                "versees", 20L
            );
            when(aideService.obtenirStatistiquesGlobales()).thenReturn(statistiques);

            // When & Then
            given()
                .when()
                    .get("/api/aides/statistiques")
                .then()
                    .statusCode(200)
                    .contentType(ContentType.JSON)
                    .body("total", equalTo(100))
                    .body("enAttente", equalTo(25))
                    .body("approuvees", equalTo(50))
                    .body("versees", equalTo(20));
        }
    }

    @Nested
    @DisplayName("Tests de sécurité")
    class SecurityTests {

        @Test
        @DisplayName("Accès non authentifié - 401")
        void testAccesNonAuthentifie() {
            given()
                .when()
                    .get("/api/aides")
                .then()
                    .statusCode(401);
        }

        @Test
        @TestSecurity(user = "membre", roles = {"membre"})
        @DisplayName("Accès non autorisé pour approbation - 403")
        void testAccesNonAutorisePourApprobation() {
            Map<String, Object> approbationData = Map.of(
                "montantApprouve", "400000.00",
                "commentaires", "Test"
            );

            given()
                .contentType(ContentType.JSON)
                .body(approbationData)
                .when()
                    .post("/api/aides/1/approuver")
                .then()
                    .statusCode(403);
        }
    }
}
