package dev.lions.unionflow.server.resource;

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

import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse;
import dev.lions.unionflow.server.service.TypeReferenceService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

/**
 * Tests d'intégration REST pour TypeReferenceResource.
 * Utilise @InjectMock pour isoler le service et couvrir toutes les branches.
 */
@QuarkusTest
class TypeReferenceResourceTest {

    private static final String BASE_PATH = "/api/v1/types-reference";

    @InjectMock
    TypeReferenceService service;

    @BeforeEach
    void setUp() {
        Mockito.reset(service);
        when(service.listerParDomaine(anyString(), any()))
                .thenReturn(List.of());
        when(service.listerDomaines())
                .thenReturn(List.of("STATUT_ORGANISATION", "TYPE_IDENTITE"));
    }

    // =========================================================================
    // listerParDomaine — GET /api/v1/types-reference?domaine=...
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET ?domaine=... retourne 200 avec liste")
    void listerParDomaine_returns200() {
        TypeReferenceResponse ref = TypeReferenceResponse.builder()
                .domaine("STATUT_ORGANISATION")
                .code("ACTIVE")
                .libelle("Active")
                .ordreAffichage(1)
                .estDefaut(false)
                .estSysteme(false)
                .build();

        when(service.listerParDomaine(eq("STATUT_ORGANISATION"), any()))
                .thenReturn(List.of(ref));

        given()
                .queryParam("domaine", "STATUT_ORGANISATION")
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET sans domaine retourne 400")
    void listerParDomaine_sansDomaine_returns400() {
        given()
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET avec domaine blanc retourne 400")
    void listerParDomaine_domaineBlank_returns400() {
        given()
                .queryParam("domaine", "   ")
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET avec domaine et organisationId retourne 200")
    void listerParDomaine_avecOrganisationId_returns200() {
        given()
                .queryParam("domaine", "STATUT_COMPTE")
                .queryParam("organisationId", UUID.randomUUID().toString())
                .when()
                .get(BASE_PATH)
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // trouverParId — GET /api/v1/types-reference/{id}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /{id} existant retourne 200")
    void trouverParId_trouve_returns200() {
        UUID id = UUID.randomUUID();
        TypeReferenceResponse ref = TypeReferenceResponse.builder()
                .domaine("TYPE_IDENTITE")
                .code("CNI")
                .libelle("Carte Nationale d'Identité")
                .ordreAffichage(1)
                .estDefaut(true)
                .estSysteme(true)
                .build();

        when(service.trouverParId(id)).thenReturn(ref);

        given()
                .pathParam("id", id)
                .when()
                .get(BASE_PATH + "/{id}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /{id} inexistant retourne 404")
    void trouverParId_inexistant_returns404() {
        UUID id = UUID.randomUUID();
        when(service.trouverParId(id))
                .thenThrow(new IllegalArgumentException("Type de référence introuvable: " + id));

        given()
                .pathParam("id", id)
                .when()
                .get(BASE_PATH + "/{id}")
                .then()
                .statusCode(404)
                .body("error", notNullValue());
    }

    // =========================================================================
    // listerDomaines — GET /api/v1/types-reference/domaines
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /domaines retourne 200")
    void listerDomaines_returns200() {
        given()
                .when()
                .get(BASE_PATH + "/domaines")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    // =========================================================================
    // trouverDefaut — GET /api/v1/types-reference/defaut
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /defaut?domaine=... retourne 200 si défaut trouvé")
    void trouverDefaut_trouve_returns200() {
        TypeReferenceResponse ref = TypeReferenceResponse.builder()
                .domaine("STATUT_ORGANISATION")
                .code("ACTIVE")
                .libelle("Active")
                .ordreAffichage(1)
                .estDefaut(true)
                .estSysteme(false)
                .build();

        when(service.trouverDefaut(eq("STATUT_ORGANISATION"), any()))
                .thenReturn(ref);

        given()
                .queryParam("domaine", "STATUT_ORGANISATION")
                .when()
                .get(BASE_PATH + "/defaut")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /defaut sans domaine retourne 400")
    void trouverDefaut_sansDomaine_returns400() {
        given()
                .when()
                .get(BASE_PATH + "/defaut")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /defaut avec domaine blanc retourne 400")
    void trouverDefaut_domaineBlank_returns400() {
        given()
                .queryParam("domaine", "")
                .when()
                .get(BASE_PATH + "/defaut")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /defaut?domaine=... retourne 404 si aucun défaut")
    void trouverDefaut_introuvable_returns404() {
        when(service.trouverDefaut(eq("DOMAINE_INCONNU"), any()))
                .thenThrow(new IllegalArgumentException("Aucune valeur par défaut pour le domaine: DOMAINE_INCONNU"));

        given()
                .queryParam("domaine", "DOMAINE_INCONNU")
                .when()
                .get(BASE_PATH + "/defaut")
                .then()
                .statusCode(404)
                .body("error", notNullValue());
    }

    // =========================================================================
    // creer — POST /api/v1/types-reference
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST retourne 201 avec données valides")
    void creer_valide_returns201() {
        TypeReferenceResponse created = TypeReferenceResponse.builder()
                .domaine("STATUT_TEST")
                .code("CODE_TEST")
                .libelle("Code Test")
                .ordreAffichage(1)
                .estDefaut(false)
                .estSysteme(false)
                .build();

        when(service.creer(any())).thenReturn(created);

        Map<String, Object> body = Map.of(
                "domaine", "STATUT_TEST",
                "code", "CODE_TEST",
                "libelle", "Code Test",
                "ordreAffichage", 1
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(201)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST avec code dupliqué retourne 400")
    void creer_codeDuplique_returns400() {
        when(service.creer(any()))
                .thenThrow(new IllegalArgumentException("Le code 'CODE_DUP' existe déjà dans le domaine 'STATUT_TEST'"));

        Map<String, Object> body = Map.of(
                "domaine", "STATUT_TEST",
                "code", "CODE_DUP",
                "libelle", "Duplicate"
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post(BASE_PATH)
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    // =========================================================================
    // modifier — PUT /api/v1/types-reference/{id}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /{id} existant retourne 200")
    void modifier_valide_returns200() {
        UUID id = UUID.randomUUID();
        TypeReferenceResponse updated = TypeReferenceResponse.builder()
                .domaine("STATUT_TEST")
                .code("CODE_MAJ")
                .libelle("Libellé mis à jour")
                .ordreAffichage(2)
                .estDefaut(false)
                .estSysteme(false)
                .build();

        when(service.modifier(eq(id), any())).thenReturn(updated);

        Map<String, Object> body = Map.of(
                "libelle", "Libellé mis à jour",
                "ordreAffichage", 2
        );

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .body(body)
                .when()
                .put(BASE_PATH + "/{id}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /{id} inexistant retourne 400")
    void modifier_introuvable_returns400() {
        UUID id = UUID.randomUUID();
        when(service.modifier(eq(id), any()))
                .thenThrow(new IllegalArgumentException("Type de référence introuvable: " + id));

        Map<String, Object> body = Map.of(
                "libelle", "Test"
        );

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .body(body)
                .when()
                .put(BASE_PATH + "/{id}")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /{id} valeur système → code immuable retourne 400")
    void modifier_valeurSysteme_codeImmuable_returns400() {
        UUID id = UUID.randomUUID();
        when(service.modifier(eq(id), any()))
                .thenThrow(new IllegalArgumentException("Le code d'une valeur système ne peut pas être modifié"));

        Map<String, Object> body = Map.of(
                "code", "NOUVEAU_CODE"
        );

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .body(body)
                .when()
                .put(BASE_PATH + "/{id}")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    // =========================================================================
    // supprimer — DELETE /api/v1/types-reference/{id}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"SUPER_ADMIN"})
    @DisplayName("DELETE /{id} existant retourne 204")
    void supprimer_existant_returns204() {
        UUID id = UUID.randomUUID();
        doNothing().when(service).supprimer(id);

        given()
                .pathParam("id", id)
                .when()
                .delete(BASE_PATH + "/{id}")
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"SUPER_ADMIN"})
    @DisplayName("DELETE /{id} inexistant retourne 400")
    void supprimer_introuvable_returns400() {
        UUID id = UUID.randomUUID();
        doThrow(new IllegalArgumentException("Type de référence introuvable: " + id))
                .when(service).supprimer(id);

        given()
                .pathParam("id", id)
                .when()
                .delete(BASE_PATH + "/{id}")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"SUPER_ADMIN"})
    @DisplayName("DELETE /{id} valeur système retourne 400")
    void supprimer_valeurSysteme_returns400() {
        UUID id = UUID.randomUUID();
        doThrow(new IllegalArgumentException("Impossible de supprimer une valeur système: CODE_SYS"))
                .when(service).supprimer(id);

        given()
                .pathParam("id", id)
                .when()
                .delete(BASE_PATH + "/{id}")
                .then()
                .statusCode(400)
                .body("error", notNullValue());
    }
}
