package dev.lions.unionflow.server.resource;

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

import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.service.KeycloakService;
import dev.lions.unionflow.server.service.OrganisationService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests ciblant la lambda {@code OrganisationResource.lambda$listerOrganisations$0} (ligne 178).
 *
 * <p>La lambda est le prédicat du {@code .filter()} appliqué lors de la recherche en mémoire pour
 * un ADMIN_ORGANISATION :
 * <pre>
 *   o -> (o.getNom() != null && o.getNom().toLowerCase().contains(term))
 *        || (o.getNomCourt() != null && o.getNomCourt().toLowerCase().contains(term))
 * </pre>
 *
 * <p>Branches à couvrir (10 instructions, 6 branches) :
 * <ol>
 *   <li>nom non null + contient le terme → inclus (branche NOM)</li>
 *   <li>nom null → fallback nomCourt non null + contient → inclus (branche NOM_COURT)</li>
 *   <li>nom non null mais ne contient pas + nomCourt null → exclu</li>
 *   <li>nom null + nomCourt null → exclu</li>
 *   <li>nom non null + ne contient pas + nomCourt non null + contient → inclus</li>
 *   <li>terme vide → pas de filtre (branche recherche trim vide)</li>
 * </ol>
 */
@QuarkusTest
@DisplayName("OrganisationResource — lambda$listerOrganisations$0 (filtre en mémoire ADMIN_ORGANISATION)")
class OrganisationResourceLambdaFilterTest {

    @InjectMock
    OrganisationService organisationService;

    @InjectMock
    KeycloakService keycloakService;

    // =========================================================================
    // Branche 1 : nom non null + contient le terme → organisation incluse
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — nom non null contenant le terme → organisation incluse")
    void listerOrganisations_adminOrg_recherche_nomContientTerme_inclus() {
        Organisation orgAvecNomCorrespondant = new Organisation();
        orgAvecNomCorrespondant.setId(UUID.randomUUID());
        orgAvecNomCorrespondant.setNom("Lions Club Abidjan");
        orgAvecNomCorrespondant.setNomCourt("LCA");
        orgAvecNomCorrespondant.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(orgAvecNomCorrespondant));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        orgAvecNomCorrespondant.getId(), "Lions Club Abidjan", "LCA",
                        null, null, null, null, null, null, true, null, null));

        given()
                .queryParam("recherche", "Lions")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200)
                .body(notNullValue());
    }

    // =========================================================================
    // Branche 2 : nom null, nomCourt non null + contient le terme → incluse
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — nom null, nomCourt non null contenant le terme → organisation incluse")
    void listerOrganisations_adminOrg_recherche_nomNull_nomCourtContientTerme_inclus() {
        Organisation orgSansNom = new Organisation();
        orgSansNom.setId(UUID.randomUUID());
        orgSansNom.setNom(null);           // nom null → branche false sur o.getNom() != null
        orgSansNom.setNomCourt("CLUBS");   // nomCourt non null + contient "club"
        orgSansNom.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(orgSansNom));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        orgSansNom.getId(), null, "CLUBS",
                        null, null, null, null, null, null, true, null, null));

        given()
                .queryParam("recherche", "club")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 3 : nom non null mais ne contient pas + nomCourt null → exclue
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — nom ne contient pas le terme, nomCourt null → organisation exclue")
    void listerOrganisations_adminOrg_recherche_nomNeContientPas_nomCourtNull_exclu() {
        Organisation orgNonCorrespondante = new Organisation();
        orgNonCorrespondante.setId(UUID.randomUUID());
        orgNonCorrespondante.setNom("Association Alpha");   // ne contient pas "beta"
        orgNonCorrespondante.setNomCourt(null);              // nomCourt null → branche false
        orgNonCorrespondante.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(orgNonCorrespondante));

        // Aucun appel à convertToSummaryResponse attendu car l'org est filtrée
        given()
                .queryParam("recherche", "beta")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 4 : nom null + nomCourt null → exclue (les deux null)
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — nom null ET nomCourt null → organisation exclue")
    void listerOrganisations_adminOrg_recherche_nomEtNomCourtNull_exclu() {
        Organisation orgToutNull = new Organisation();
        orgToutNull.setId(UUID.randomUUID());
        orgToutNull.setNom(null);      // nom null → premier prédicat false
        orgToutNull.setNomCourt(null); // nomCourt null → deuxième prédicat false → exclu
        orgToutNull.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(orgToutNull));

        given()
                .queryParam("recherche", "quelquechose")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 5 : nom non null + ne contient pas + nomCourt non null + contient → incluse
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — nom ne contient pas, nomCourt contient le terme → organisation incluse")
    void listerOrganisations_adminOrg_recherche_nomNeContientPas_nomCourtContient_inclus() {
        Organisation orgNomCourtMatch = new Organisation();
        orgNomCourtMatch.setId(UUID.randomUUID());
        orgNomCourtMatch.setNom("Association Nationale");  // ne contient pas "lc"
        orgNomCourtMatch.setNomCourt("LC-Abj");            // contient "lc"
        orgNomCourtMatch.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(orgNomCourtMatch));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        orgNomCourtMatch.getId(), "Association Nationale", "LC-Abj",
                        null, null, null, null, null, null, true, null, null));

        given()
                .queryParam("recherche", "lc")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 6 : recherche vide → pas de filtre, retourne tout
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — terme de recherche vide → aucun filtre appliqué, toutes les orgs retournées")
    void listerOrganisations_adminOrg_rechercheVide_aucunFiltre() {
        Organisation org1 = new Organisation();
        org1.setId(UUID.randomUUID());
        org1.setNom("Org A");
        org1.setActif(true);

        Organisation org2 = new Organisation();
        org2.setId(UUID.randomUUID());
        org2.setNom("Org B");
        org2.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(org1, org2));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        UUID.randomUUID(), "Org", null, null, null, null, null, null, null, true, null, null));

        // Pas de queryParam "recherche" → isBlank → pas de filtre
        given()
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 7 : terme uniquement espaces → pas de filtre (trim = "")
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Filtre lambda — terme de recherche uniquement espaces → pas de filtre (trim vide)")
    void listerOrganisations_adminOrg_rechercheEspaces_pasDeFiltre() {
        Organisation org = new Organisation();
        org.setId(UUID.randomUUID());
        org.setNom("Org Espace Test");
        org.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(org));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        org.getId(), "Org Espace Test", null, null, null, null, null, null, null, true, null, null));

        given()
                .queryParam("recherche", "   ")
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 8 : pagination en mémoire pour ADMIN_ORGANISATION (from/to)
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Pagination en mémoire — ADMIN_ORGANISATION avec plusieurs orgs et page 0 size 1")
    void listerOrganisations_adminOrg_paginationMemoire_page0Size1() {
        Organisation org1 = new Organisation();
        org1.setId(UUID.randomUUID());
        org1.setNom("Org Pag 1");
        org1.setActif(true);

        Organisation org2 = new Organisation();
        org2.setId(UUID.randomUUID());
        org2.setNom("Org Pag 2");
        org2.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(org1, org2));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(
                        UUID.randomUUID(), "Org", null, null, null, null, null, null, null, true, null, null));

        given()
                .queryParam("page", 0)
                .queryParam("size", 1)
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Branche 9 : pagination — page au-delà de la liste (from >= size)
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("Pagination en mémoire — page hors limites retourne liste vide (from=10, liste=2)")
    void listerOrganisations_adminOrg_paginationHorsLimites_retourneVide() {
        Organisation org1 = new Organisation();
        org1.setId(UUID.randomUUID());
        org1.setNom("Org A");
        org1.setActif(true);

        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenReturn(List.of(org1));

        given()
                .queryParam("page", 10)
                .queryParam("size", 20)
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // Error cases
    // =========================================================================

    @Test
    @DisplayName("GET /api/organisations sans authentification retourne 401")
    void listerOrganisations_sansAuthentification_returns401() {
        given()
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(401);
    }

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("GET /api/organisations quand service lève exception retourne 500")
    void listerOrganisations_serviceException_returns500() {
        when(organisationService.listerOrganisationsPourUtilisateur(any()))
                .thenThrow(new RuntimeException("Erreur base de données"));

        given()
                .when()
                .get("/api/organisations")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("GET /api/organisations/{id} avec ID inexistant retourne 404")
    void obtenirOrganisation_notFound_returns404() {
        when(organisationService.trouverParId(any(UUID.class)))
                .thenReturn(java.util.Optional.empty());

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