package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;

import dev.lions.unionflow.server.entity.Adresse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.MembreRoleRepository;
import dev.lions.unionflow.server.repository.TypeReferenceRepository;
import dev.lions.unionflow.server.service.MembreImportExportService;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests ciblant les lambdas internes de {@code MembreService.calculateSearchStatistics}
 * (ligne 697-718) via réflexion.
 *
 * <p>Ces lambdas ne sont pas atteintes par {@link MembreServiceTest} car tous les membres
 * fixture y ont des listes vides ({@code membresOrganisations} et {@code adresses}).
 */
@QuarkusTest
class MembreServiceSearchStatsLambdaTest {

    @Inject
    MembreService membreService;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    MembreRoleRepository membreRoleRepository;

    @InjectMock
    TypeReferenceRepository typeReferenceRepository;

    @InjectMock
    MembreImportExportService membreImportExportService;

    @InjectMock
    OrganisationService organisationService;

    @InjectMock
    SecurityIdentity securityIdentity;

    // =========================================================================
    // Helpers
    // =========================================================================

    private Object invokeCalculerStatistiques(List<Membre> membres) throws Exception {
        Method method = MembreService.class.getDeclaredMethod("calculateSearchStatistics", List.class);
        method.setAccessible(true);
        return method.invoke(membreService, membres);
    }

    // =========================================================================
    // Lambda ligne 697-703 : flatMap membresOrganisations + map organisation.getId()
    // =========================================================================

    @Test
    @DisplayName("calculateSearchStatistics avec membresOrganisations non-null et organisation non-null couvre lambda$11 ligne 700")
    void calculateSearchStatistics_membresOrgNonNull_withOrg_coversMapOrgIdLambda() throws Exception {
        // Organisation avec un ID non-null
        Organisation org = new Organisation();
        org.setId(UUID.randomUUID());

        MembreOrganisation mo = new MembreOrganisation();
        mo.setOrganisation(org);

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Diallo");
        membre.setPrenom("Amadou");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1990, 1, 1));
        membre.setMembresOrganisations(List.of(mo));
        membre.setAdresses(new ArrayList<>());

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    @Test
    @DisplayName("calculateSearchStatistics avec MembreOrganisation.organisation null couvre branche false lambda ligne 700")
    void calculateSearchStatistics_membresOrgNonNull_orgNull_coversNullOrgBranch() throws Exception {
        // MembreOrganisation avec organisation null → map retourne null → filter(Objects::nonNull) retire
        MembreOrganisation moNullOrg = new MembreOrganisation();
        moNullOrg.setOrganisation(null);

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Bah");
        membre.setPrenom("Isatou");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1992, 5, 15));
        membre.setMembresOrganisations(List.of(moNullOrg));
        membre.setAdresses(new ArrayList<>());

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    @Test
    @DisplayName("calculateSearchStatistics avec membresOrganisations null couvre branche false flatMap ligne 697")
    void calculateSearchStatistics_membresOrgNull_coversStreamEmptyBranch() throws Exception {
        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Camara");
        membre.setPrenom("Fatou");
        membre.setActif(false);
        membre.setDateNaissance(null);
        membre.setMembresOrganisations(null); // → Stream.empty() branch
        membre.setAdresses(new ArrayList<>());

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    // =========================================================================
    // Lambda ligne 714-718 : flatMap adresses + map region + filter non-null non-empty
    // =========================================================================

    @Test
    @DisplayName("calculateSearchStatistics avec adresses non-null et region non-vide couvre lambdas ligne 714-716")
    void calculateSearchStatistics_adressesNonNull_withRegion_coversAdresseLambdas() throws Exception {
        Adresse adresse = new Adresse();
        adresse.setRegion("Dakar");
        adresse.setTypeAdresse("DOMICILE");

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Sow");
        membre.setPrenom("Mariama");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1995, 3, 20));
        membre.setMembresOrganisations(new ArrayList<>());
        membre.setAdresses(List.of(adresse));

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    @Test
    @DisplayName("calculateSearchStatistics avec adresses null couvre branche false flatMap ligne 714")
    void calculateSearchStatistics_adressesNull_coversStreamEmptyBranch() throws Exception {
        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Keita");
        membre.setPrenom("Awa");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1988, 7, 10));
        membre.setMembresOrganisations(new ArrayList<>());
        membre.setAdresses(null); // → Stream.empty() branch at line 714

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    @Test
    @DisplayName("calculateSearchStatistics avec adresse region null couvre filtre non-null ligne 716")
    void calculateSearchStatistics_adressesWithNullRegion_coversFilterNullRegion() throws Exception {
        Adresse adresseNullRegion = new Adresse();
        adresseNullRegion.setRegion(null); // → filtrée par filter(r -> r != null && !r.isEmpty())
        adresseNullRegion.setTypeAdresse("DOMICILE");

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Traore");
        membre.setPrenom("Hawa");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1993, 11, 5));
        membre.setMembresOrganisations(new ArrayList<>());
        membre.setAdresses(List.of(adresseNullRegion));

        Object stats = invokeCalculerStatistiques(List.of(membre));

        assertThat(stats).isNotNull();
    }

    // =========================================================================
    // Vérification que la liste vide déclenche le chemin early-return
    // =========================================================================

    @Test
    @DisplayName("calculateSearchStatistics avec liste vide retourne des statistiques à 0")
    void calculateSearchStatistics_emptyList_returnsZeroStats() throws Exception {
        Object stats = invokeCalculerStatistiques(Collections.emptyList());

        assertThat(stats).isNotNull();
    }

    // =========================================================================
    // L710: filter(r -> r != null && !r.isEmpty()) — branche r != null && r.isEmpty() (vide → filtre)
    // La branche r==null est couverte par calculateSearchStatistics_adressesWithNullRegion_coversFilterNullRegion
    // La branche r!= null && isEmpty() manque — couverte par region="" (chaîne vide)
    // =========================================================================

    @Test
    @DisplayName("calculateSearchStatistics avec adresse region vide → L710 filter r.isEmpty() true → filtrée")
    void calculateSearchStatistics_adressesWithEmptyRegion_L710FilterEmptyTrue() throws Exception {
        // region = "" → r != null = true, r.isEmpty() = true → !r.isEmpty() = false → filtrée
        Adresse adresseEmptyRegion = new Adresse();
        adresseEmptyRegion.setRegion(""); // vide → filter: r != null && !isEmpty() → false → filtrée
        adresseEmptyRegion.setTypeAdresse("DOMICILE");

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Coulibaly");
        membre.setPrenom("Fatoumata");
        membre.setActif(true);
        membre.setDateNaissance(LocalDate.of(1995, 3, 10));
        membre.setMembresOrganisations(new ArrayList<>());
        membre.setAdresses(List.of(adresseEmptyRegion));

        Object stats = invokeCalculerStatistiques(List.of(membre));
        assertThat(stats).isNotNull();
    }
}
