package dev.lions.unionflow.server.resource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
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.security.identity.SecurityIdentity;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;

import java.security.Principal;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests CDI directs pour {@link OrganisationResource} couvrant les branches
 * inaccessibles via HTTP standard (principal null).
 *
 * <ul>
 *   <li>L75 : {@code listerMesOrganisations()} — principal null → email null → liste vide</li>
 *   <li>L172 : {@code listerOrganisations()} — ADMIN_ORGANISATION + principal null → chemin global</li>
 * </ul>
 */
@QuarkusTest
@DisplayName("OrganisationResource — branches de couverture manquantes")
class OrganisationResourceMissingBranchesTest {

    @Inject
    OrganisationResource organisationResource;

    @InjectMock
    OrganisationService organisationService;

    @InjectMock
    KeycloakService keycloakService;

    @InjectMock
    SecurityIdentity securityIdentity;

    // =========================================================================
    // listerMesOrganisations L75 : principal null → email null → liste vide
    // =========================================================================

    @Test
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("listerMesOrganisations — principal null → email null → liste vide (couvre L75)")
    void listerMesOrganisations_principalNull_retourneListeVide() {
        when(securityIdentity.getPrincipal()).thenReturn(null);

        Response response = organisationResource.listerMesOrganisations();

        assertThat(response.getStatus()).isEqualTo(200);
        @SuppressWarnings("unchecked")
        List<OrganisationResponse> body = (List<OrganisationResponse>) response.getEntity();
        assertThat(body).isEmpty();
    }

    // =========================================================================
    // listerOrganisations L172 : ADMIN_ORGANISATION + principal null → chemin global (else)
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + principal null → chemin global (couvre L172)")
    void listerOrganisations_adminOrg_principalNull_cheminGlobal() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION"));
        when(securityIdentity.getPrincipal()).thenReturn(null);
        when(organisationService.listerOrganisationsActives(anyInt(), anyInt())).thenReturn(List.of());
        when(organisationService.compterOrganisationsActives()).thenReturn(0L);

        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, null);

        assertThat(result).isNotNull();
    }

    // =========================================================================
    // listerOrganisations L175-180 : ADMIN_ORGANISATION + principal non-null + recherche non-vide
    // → filtre appliqué sur les organisations (lambda getNom/getNomCourt)
    // =========================================================================

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + recherche → filtre lambda getNom (couvre L175-180)")
    void listerOrganisations_adminOrg_avecRecherche_filtreParNom() {
        Principal principal = () -> "orgadmin@test.com";
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION"));
        when(securityIdentity.getPrincipal()).thenReturn(principal);

        // Organisation dont le nom contient "test" — passera le filtre
        Organisation org1 = new Organisation();
        org1.setId(UUID.randomUUID());
        org1.setNom("Organisation Test");
        org1.setNomCourt(null);

        // Organisation dont nomCourt contient "test" — passera le filtre via nomCourt
        Organisation org2 = new Organisation();
        org2.setId(UUID.randomUUID());
        org2.setNom(null);
        org2.setNomCourt("Org Test Court");

        // Organisation qui ne correspond pas
        Organisation org3 = new Organisation();
        org3.setId(UUID.randomUUID());
        org3.setNom("Autre Association");
        org3.setNomCourt("AA");

        when(organisationService.listerOrganisationsPourUtilisateur("orgadmin@test.com"))
                .thenReturn(List.of(org1, org2, org3));
        when(organisationService.convertToSummaryResponse(any(Organisation.class)))
                .thenReturn(new OrganisationSummaryResponse(null, null, null, null, null, null, null, null, null, null, null, null));

        // recherche = "test" → filtre les organisations dont nom ou nomCourt contient "test"
        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, "test");

        assertThat(result).isNotNull();
        // org1 et org2 correspondent, org3 non → 2 résultats
        assertThat(result.getData()).hasSize(2);
    }

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + recherche → filtre lambda avec nom null et nomCourt null")
    void listerOrganisations_adminOrg_avecRecherche_nomEtNomCourtNull_filtre() {
        Principal principal = () -> "orgadmin@test.com";
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION"));
        when(securityIdentity.getPrincipal()).thenReturn(principal);

        // Organisation avec nom=null et nomCourt=null — ne passera pas le filtre
        Organisation org = new Organisation();
        org.setId(UUID.randomUUID());
        org.setNom(null);
        org.setNomCourt(null);

        when(organisationService.listerOrganisationsPourUtilisateur("orgadmin@test.com"))
                .thenReturn(List.of(org));

        // recherche non vide → filtre → nom null && nomCourt null → aucun résultat
        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, "quelquechose");

        assertThat(result).isNotNull();
        assertThat(result.getData()).isEmpty();
    }

    // =========================================================================
    // listerOrganisations L170 : ADMIN_ORGANISATION + ADMIN → onlyOrgAdmin=false
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN_ORGANISATION", "ADMIN"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + ADMIN → onlyOrgAdmin=false, chemin global (couvre L170)")
    void listerOrganisations_adminOrgPlusAdmin_onlyOrgAdminFalse() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION", "ADMIN"));
        when(organisationService.listerOrganisationsActives(anyInt(), anyInt())).thenReturn(List.of());
        when(organisationService.compterOrganisationsActives()).thenReturn(0L);

        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, null);

        assertThat(result).isNotNull();
    }

    // =========================================================================
    // listerOrganisations L171 : ADMIN_ORGANISATION + SUPER_ADMIN → onlyOrgAdmin=false
    // =========================================================================

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"ADMIN_ORGANISATION", "SUPER_ADMIN"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + SUPER_ADMIN → onlyOrgAdmin=false (couvre L171)")
    void listerOrganisations_adminOrgPlusSuperAdmin_onlyOrgAdminFalse() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION", "SUPER_ADMIN"));
        when(organisationService.listerOrganisationsActives(anyInt(), anyInt())).thenReturn(List.of());
        when(organisationService.compterOrganisationsActives()).thenReturn(0L);

        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, null);

        assertThat(result).isNotNull();
    }

    // =========================================================================
    // listerOrganisations L188 : onlyOrgAdmin=false + recherche non-null mais blank
    // → !recherche.trim().isEmpty()=false → else branch
    // =========================================================================

    @Test
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("listerOrganisations — MEMBRE + recherche blank → else if isEmpty()=true → else (couvre L188)")
    void listerOrganisations_membre_rechercheBlank_elseElse() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("MEMBRE"));
        when(organisationService.listerOrganisationsActives(anyInt(), anyInt())).thenReturn(List.of());
        when(organisationService.compterOrganisationsActives()).thenReturn(0L);

        // recherche non-null mais blank → trim().isEmpty()=true → condition false → else
        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, "   ");

        assertThat(result).isNotNull();
    }

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

    @Test
    @TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
    @DisplayName("listerMesOrganisations — service lève exception → exception propagée")
    void listerMesOrganisations_serviceException_propagee() {
        Principal principal = () -> "membre@test.com";
        when(securityIdentity.getPrincipal()).thenReturn(principal);
        when(organisationService.listerOrganisationsPourUtilisateur(anyString()))
                .thenThrow(new RuntimeException("Erreur base de données"));

        org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class,
                () -> organisationResource.listerMesOrganisations());
    }

    @Test
    @TestSecurity(user = "superadmin@test.com", roles = {"SUPER_ADMIN"})
    @DisplayName("listerOrganisations — SUPER_ADMIN + service retourne liste vide → résultat non null")
    void listerOrganisations_superAdmin_listeVide_retourneResultatNonNull() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("SUPER_ADMIN"));
        when(organisationService.listerOrganisationsActives(anyInt(), anyInt())).thenReturn(List.of());
        when(organisationService.compterOrganisationsActives()).thenReturn(0L);

        PagedResponse<OrganisationSummaryResponse> result = organisationResource.listerOrganisations(
                0, 20, null);

        assertThat(result).isNotNull();
        assertThat(result.getData()).isEmpty();
    }

    @Test
    @TestSecurity(user = "orgadmin@test.com", roles = {"ADMIN_ORGANISATION"})
    @DisplayName("listerOrganisations — ADMIN_ORGANISATION + service lève exception → exception propagée")
    void listerOrganisations_adminOrg_serviceException_propagee() {
        when(securityIdentity.getRoles()).thenReturn(Set.of("ADMIN_ORGANISATION"));
        Principal principal = () -> "orgadmin@test.com";
        when(securityIdentity.getPrincipal()).thenReturn(principal);
        when(organisationService.listerOrganisationsPourUtilisateur(anyString()))
                .thenThrow(new RuntimeException("Accès base de données impossible"));

        org.junit.jupiter.api.Assertions.assertThrows(RuntimeException.class,
                () -> organisationResource.listerOrganisations(0, 20, null));
    }
}
