package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityResponse;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Evenement;
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.CotisationRepository;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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 pour {@link DashboardServiceImpl#getRecentActivities} — branche {@code appartiendAOrg=true}.
 *
 * <p>Sans ce test, les lignes 191-201 (construction du RecentActivityResponse pour un membre
 * appartenant à l'organisation) et les lambdas 188-189 (anyMatch) ne sont jamais atteintes,
 * car les tests existants ne fournissent pas de membre lié à l'org ciblée.
 */
@QuarkusTest
@DisplayName("DashboardServiceImpl.getRecentActivities — membre appartenant à l'org")
class DashboardServiceImplRecentActivitiesTest {

    @Inject
    DashboardServiceImpl dashboardService;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    EvenementRepository evenementRepository;

    @InjectMock
    CotisationRepository cotisationRepository;

    @InjectMock
    DemandeAideRepository demandeAideRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    private static final UUID ORG_ID = UUID.fromString("11111111-2222-3333-4444-555555555555");

    @Test
    @DisplayName("getRecentActivities avec membre appartenant à l'org — couvre lignes 191-201 (appartiendAOrg=true)")
    void getRecentActivities_membreAppartientAOrg_addsActivityToList() {
        // Organisation avec l'UUID fixe
        Organisation org = new Organisation();
        org.setId(ORG_ID);

        // MembreOrganisation qui lie membre → organisation (couvre lambda anyMatch lignes 188-189)
        MembreOrganisation mo = new MembreOrganisation();
        mo.setOrganisation(org);

        // Membre avec membresOrganisations non null contenant mo
        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Diallo");
        membre.setPrenom("Amadou");
        membre.setActif(true);
        membre.setNumeroMembre("M001");
        membre.setEmail("amadou.diallo@test.com");
        membre.setDateCreation(LocalDateTime.now().minusDays(1));
        membre.setMembresOrganisations(List.of(mo));

        // rechercheAvancee retourne notre membre → entre dans la boucle for (ligne 185)
        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(List.of(membre));

        // Pas d'événements ni cotisations → simplifie le test
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(Collections.emptyList());

        // Appel — orgId valide → anyMatch retourne true → lines 191-201 exécutées
        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities).isNotEmpty();
        assertThat(activities.get(0).getType()).isEqualTo("member");
        assertThat(activities.get(0).getTitle()).isEqualTo("Nouveau membre inscrit");
        assertThat(activities.get(0).getUserName()).isEqualTo("Amadou Diallo");
    }

    @Test
    @DisplayName("getRecentActivities avec mo.getOrganisation() == null couvre la branche false du anyMatch (lambda$2:188)")
    void getRecentActivities_moOrganisationNull_appartiendAOrgFalse() {
        // MembreOrganisation avec organisation == null → anyMatch retourne false → pas d'activité
        MembreOrganisation moSansOrg = new MembreOrganisation();
        moSansOrg.setOrganisation(null);

        Membre membre = new Membre();
        membre.setId(UUID.randomUUID());
        membre.setNom("Kone");
        membre.setPrenom("Seydou");
        membre.setActif(true);
        membre.setNumeroMembre("M002");
        membre.setEmail("seydou.kone@test.com");
        membre.setDateCreation(LocalDateTime.now().minusDays(2));
        membre.setMembresOrganisations(List.of(moSansOrg));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(List.of(membre));
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(Collections.emptyList());

        // mo.getOrganisation() == null → anyMatch retourne false → activities vide
        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities).isEmpty();
    }

    @Test
    @DisplayName("getRecentActivities avec événement lié à l'org couvre la branche true du filtre événements (lambda$3:207)")
    void getRecentActivities_evenementLieAOrg_addsEventActivity() {
        // Événement avec organisation correspondant à ORG_ID → filtre retourne true → activité ajoutée
        Organisation org = new Organisation();
        org.setId(ORG_ID);
        org.setNom("Org Principale");

        Evenement evenement = new Evenement();
        evenement.setId(UUID.randomUUID());
        evenement.setTitre("Conférence annuelle");
        evenement.setOrganisation(org);
        evenement.setDateCreation(LocalDateTime.now().minusDays(1));

        // Pas de membres retournés → on se concentre sur les événements
        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(List.of(evenement));
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(Collections.emptyList());

        // e.getOrganisation() != null && ids égaux → true → activité "event" ajoutée
        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities).isNotEmpty();
        assertThat(activities.stream().anyMatch(a -> "event".equals(a.getType()))).isTrue();
        assertThat(activities.stream().filter(a -> "event".equals(a.getType()))
                .findFirst().get().getTitle()).isEqualTo("Événement créé");
    }

    @Test
    @DisplayName("getRecentActivities avec événement dont getOrganisation() est null couvre la branche false du filtre événements (lambda$3:207)")
    void getRecentActivities_evenementSansOrg_notAdded() {
        // Événement avec organisation == null → filtre retourne false → pas d'activité event
        Evenement evenementSansOrg = new Evenement();
        evenementSansOrg.setId(UUID.randomUUID());
        evenementSansOrg.setTitre("Événement sans org");
        evenementSansOrg.setOrganisation(null);
        evenementSansOrg.setDateCreation(LocalDateTime.now().minusDays(3));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(List.of(evenementSansOrg));
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(Collections.emptyList());

        // e.getOrganisation() == null → false → aucune activité event
        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities.stream().noneMatch(a -> "event".equals(a.getType()))).isTrue();
    }

    // -----------------------------------------------------------------------
    // Branches cotisation : cotisation.getMembre() != null && org correspond (L230-244)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("getRecentActivities avec cotisation liée à l'org couvre la branche true cotisation (L230-244)")
    void getRecentActivities_cotisationLieeAOrg_addsCotisationActivity() {
        Organisation org = new Organisation();
        org.setId(ORG_ID);

        Membre membreCotisant = new Membre();
        membreCotisant.setId(UUID.randomUUID());
        membreCotisant.setNom("Traoré");
        membreCotisant.setPrenom("Mamadou");

        Cotisation cotisation = new Cotisation();
        cotisation.setId(UUID.randomUUID());
        cotisation.setMembre(membreCotisant);
        cotisation.setOrganisation(org);
        cotisation.setMontantPaye(new BigDecimal("10000"));
        cotisation.setCodeDevise("XOF");
        cotisation.setDatePaiement(LocalDateTime.now().minusDays(1));
        cotisation.setDateCreation(LocalDateTime.now().minusDays(1));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(List.of(cotisation));

        // cotisation.getMembre() != null && org.getId().equals(ORG_ID) → true → activité "contribution"
        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities).isNotEmpty();
        assertThat(activities.stream().anyMatch(a -> "contribution".equals(a.getType()))).isTrue();
        assertThat(activities.stream().filter(a -> "contribution".equals(a.getType()))
                .findFirst().get().getTitle()).isEqualTo("Cotisation reçue");
    }

    @Test
    @DisplayName("getRecentActivities avec cotisation sans membre couvre la branche false (L230 membre==null)")
    void getRecentActivities_cotisationSansMembre_notAdded() {
        Organisation org = new Organisation();
        org.setId(ORG_ID);

        // Cotisation sans membre → condition L230 = false → pas d'activité contribution
        Cotisation cotisationSansMembre = new Cotisation();
        cotisationSansMembre.setId(UUID.randomUUID());
        cotisationSansMembre.setMembre(null);
        cotisationSansMembre.setOrganisation(org);
        cotisationSansMembre.setMontantPaye(new BigDecimal("5000"));
        cotisationSansMembre.setCodeDevise("XOF");
        cotisationSansMembre.setDateCreation(LocalDateTime.now().minusDays(2));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(List.of(cotisationSansMembre));

        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities.stream().noneMatch(a -> "contribution".equals(a.getType()))).isTrue();
    }

    @Test
    @DisplayName("getRecentActivities avec cotisation dont org ne correspond pas couvre la branche false org mismatch (L232)")
    void getRecentActivities_cotisationOrgNonCorrespondante_notAdded() {
        Organisation autreOrg = new Organisation();
        autreOrg.setId(UUID.randomUUID()); // UUID différent de ORG_ID

        Membre membreCotisant = new Membre();
        membreCotisant.setId(UUID.randomUUID());
        membreCotisant.setNom("Ba");
        membreCotisant.setPrenom("Fatou");

        // Cotisation avec membre non null mais org différente → condition L232 = false
        Cotisation cotisationAutreOrg = new Cotisation();
        cotisationAutreOrg.setId(UUID.randomUUID());
        cotisationAutreOrg.setMembre(membreCotisant);
        cotisationAutreOrg.setOrganisation(autreOrg);
        cotisationAutreOrg.setMontantPaye(new BigDecimal("3000"));
        cotisationAutreOrg.setCodeDevise("XOF");
        cotisationAutreOrg.setDateCreation(LocalDateTime.now().minusDays(1));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(List.of(cotisationAutreOrg));

        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities.stream().noneMatch(a -> "contribution".equals(a.getType()))).isTrue();
    }

    @Test
    @DisplayName("getRecentActivities avec cotisation dont datePaiement est null utilise dateCreation (L240-242)")
    void getRecentActivities_cotisationSansDatePaiement_utiliseDateCreation() {
        Organisation org = new Organisation();
        org.setId(ORG_ID);

        Membre membreCotisant = new Membre();
        membreCotisant.setId(UUID.randomUUID());
        membreCotisant.setNom("Sylla");
        membreCotisant.setPrenom("Ibrahim");

        // datePaiement null → L241 utilise dateCreation (branche : cotisation.getDatePaiement() != null ? ... : dateCreation)
        Cotisation cotisationSansDatePaiement = new Cotisation();
        cotisationSansDatePaiement.setId(UUID.randomUUID());
        cotisationSansDatePaiement.setMembre(membreCotisant);
        cotisationSansDatePaiement.setOrganisation(org);
        cotisationSansDatePaiement.setMontantPaye(new BigDecimal("2000"));
        cotisationSansDatePaiement.setCodeDevise("XOF");
        cotisationSansDatePaiement.setDatePaiement(null);
        cotisationSansDatePaiement.setDateCreation(LocalDateTime.now().minusDays(3));

        when(membreRepository.rechercheAvancee(
                isNull(), eq(true), isNull(), isNull(), any(Page.class), any(Sort.class)))
                .thenReturn(Collections.emptyList());
        when(evenementRepository.listAll()).thenReturn(Collections.emptyList());
        when(cotisationRepository.rechercheAvancee(
                isNull(), eq("PAYEE"), isNull(), isNull(), isNull(), any(Page.class)))
                .thenReturn(List.of(cotisationSansDatePaiement));

        List<RecentActivityResponse> activities = dashboardService.getRecentActivities(
                ORG_ID.toString(), "user@test.com", 10);

        assertThat(activities.stream().anyMatch(a -> "contribution".equals(a.getType()))).isTrue();
        // timestamp doit être dateCreation (non null)
        activities.stream().filter(a -> "contribution".equals(a.getType()))
                .findFirst().ifPresent(a -> assertThat(a.getTimestamp()).isNotNull());
    }
}
