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.Mockito.when;

import java.util.ArrayList;

import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.mapper.DemandeAideMapper;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
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.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Tests ciblant les branches non couvertes dans {@link DemandeAideService} :
 *
 * <ul>
 *   <li><b>L329</b> — branche {@code else if (!demande.getTypeAide().equals(valeur)) return false}
 *       dans {@code correspondAuxFiltres()} — typeAide valeur unique non correspondante.</li>
 *   <li><b>L337</b> — branche {@code else if (!demande.getStatut().equals(valeur)) return false}
 *       — statut valeur unique non correspondant.</li>
 *   <li><b>L365</b> — branche {@code return comparaisonScore} dans {@code comparerParPriorite()}
 *       — deux demandes avec des scores différents.</li>
 * </ul>
 *
 * <p>Ces tests utilisent {@code @InjectMock} pour {@link DemandeAideRepository} et
 * {@link DemandeAideMapper} afin de contrôler les données retournées.
 *
 * <p><b>Note</b> : Les branches L99 et L214 qui nécessitent de stubber
 * {@code repository.findById()} sont couvertes dans
 * {@link DemandeAideServiceL99L214Test} (approche @TestTransaction + vrais repositories).
 */
@QuarkusTest
@DisplayName("DemandeAideService — branches L329, L337, L365")
class DemandeAideServiceBranchesMissingTest {

    @Inject
    DemandeAideService demandeAideService;

    @InjectMock
    DemandeAideRepository demandeAideRepository;

    @InjectMock
    DemandeAideMapper demandeAideMapper;

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

    private UUID randomId() {
        return UUID.randomUUID();
    }

    /**
     * Construit un {@link DemandeAideResponse} minimal valide pour éviter les NPE dans
     * {@code calculerScorePriorite()} qui accède à {@code getPriorite()}, {@code getTypeAide()},
     * {@code getMontantDemande()} et {@code getDateCreation()}.
     */
    private DemandeAideResponse buildMinimalResponse(UUID id, UUID membreDemandeurId) {
        DemandeAideResponse r = new DemandeAideResponse();
        r.setId(id);
        r.setStatut(StatutAide.EN_ATTENTE);
        r.setPriorite(PrioriteAide.NORMALE);
        r.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        r.setMontantDemande(new BigDecimal("100.00"));
        r.setDateCreation(LocalDateTime.now());
        r.setMembreDemandeurId(membreDemandeurId);
        return r;
    }

    // =========================================================================
    // L329 — correspondAuxFiltres() — typeAide liste non correspondante
    //   if (valeur instanceof List<?> liste) { if (!liste.contains(...)) return false; }
    // =========================================================================

    @Nested
    @DisplayName("L329 — correspondAuxFiltres() typeAide liste — demande non contenue → return false")
    class CorrespondAuxFiltres_L329_Liste {

        @Test
        @DisplayName("L329 liste : demande AIDE_ALIMENTAIRE exclue par filtre typeAide=[TRANSPORT] (liste sans AIDE_ALIMENTAIRE)")
        void correspondAuxFiltres_typeAideListeNonContenue_retourneFalse_L329() {
            DemandeAide entity = new DemandeAide();
            entity.setId(randomId());

            DemandeAideResponse resp = buildMinimalResponse(entity.getId(), randomId());
            resp.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
            resp.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(resp);

            // valeur est une List → branche instanceof List true → !liste.contains(AIDE_ALIMENTAIRE) = true → return false
            Map<String, Object> filtres = Map.of("typeAide", List.of(TypeAide.TRANSPORT));

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("L329 liste : AIDE_ALIMENTAIRE exclue car absente de [TRANSPORT]")
                    .isEmpty();
        }

        @Test
        @DisplayName("L329 liste branche false : demande TRANSPORT incluse par filtre typeAide=[TRANSPORT]")
        void correspondAuxFiltres_typeAideListeContenue_inclutDemande() {
            DemandeAide entity = new DemandeAide();
            entity.setId(randomId());

            DemandeAideResponse resp = buildMinimalResponse(entity.getId(), randomId());
            resp.setTypeAide(TypeAide.TRANSPORT);
            resp.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(resp);

            Map<String, Object> filtres = Map.of("typeAide", List.of(TypeAide.TRANSPORT));

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("TRANSPORT est dans [TRANSPORT] → demande incluse")
                    .hasSize(1);
        }
    }

    // =========================================================================
    // L329 — correspondAuxFiltres() — typeAide valeur unique non correspondante
    //   else if (!demande.getTypeAide().equals(valeur)) return false;
    // =========================================================================

    @Nested
    @DisplayName("L329 — correspondAuxFiltres() typeAide valeur unique non correspondante")
    class CorrespondAuxFiltres_L329 {

        @Test
        @DisplayName("L329 : demande avec AIDE_ALIMENTAIRE exclue par filtre typeAide=TRANSPORT (valeur unique)")
        void correspondAuxFiltres_typeAideValeurUniqueNonCorrespondante_retourneFalse_L329() {
            DemandeAide entityAlimentaire = new DemandeAide();
            entityAlimentaire.setId(randomId());

            DemandeAideResponse responseAlimentaire = buildMinimalResponse(entityAlimentaire.getId(), randomId());
            responseAlimentaire.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
            responseAlimentaire.setStatut(StatutAide.EN_ATTENTE);
            responseAlimentaire.setPriorite(PrioriteAide.NORMALE);
            responseAlimentaire.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entityAlimentaire));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(responseAlimentaire);

            // Filtre par typeAide = TRANSPORT (valeur unique, pas List) → demande exclue
            // → L329 : else if (!demande.getTypeAide().equals(valeur)) return false
            Map<String, Object> filtres = Map.of("typeAide", TypeAide.TRANSPORT);

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("L329 : demande avec typeAide AIDE_ALIMENTAIRE exclue par filtre TRANSPORT")
                    .isEmpty();
        }
    }

    // =========================================================================
    // L337 — correspondAuxFiltres() — statut liste non correspondant
    //   if (valeur instanceof List<?> liste) { if (!liste.contains(...)) return false; }
    // =========================================================================

    @Nested
    @DisplayName("L337 — correspondAuxFiltres() statut liste — demande non contenue → return false")
    class CorrespondAuxFiltres_L337_Liste {

        @Test
        @DisplayName("L337 liste : demande EN_ATTENTE exclue par filtre statut=[VERSEE] (liste sans EN_ATTENTE)")
        void correspondAuxFiltres_statutListeNonContenu_retourneFalse_L337() {
            DemandeAide entity = new DemandeAide();
            entity.setId(randomId());

            DemandeAideResponse resp = buildMinimalResponse(entity.getId(), randomId());
            resp.setStatut(StatutAide.EN_ATTENTE);
            resp.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
            resp.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(resp);

            // valeur est une List → branche instanceof List true → !liste.contains(EN_ATTENTE) = true → return false
            Map<String, Object> filtres = Map.of("statut", List.of(StatutAide.VERSEE));

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("L337 liste : EN_ATTENTE exclue car absente de [VERSEE]")
                    .isEmpty();
        }

        @Test
        @DisplayName("L337 liste branche false : demande VERSEE incluse par filtre statut=[VERSEE]")
        void correspondAuxFiltres_statutListeContenu_inclutDemande() {
            DemandeAide entity = new DemandeAide();
            entity.setId(randomId());

            DemandeAideResponse resp = buildMinimalResponse(entity.getId(), randomId());
            resp.setStatut(StatutAide.VERSEE);
            resp.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
            resp.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(resp);

            Map<String, Object> filtres = Map.of("statut", List.of(StatutAide.VERSEE));

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("VERSEE est dans [VERSEE] → demande incluse")
                    .hasSize(1);
        }
    }

    // =========================================================================
    // L337 — correspondAuxFiltres() — statut valeur unique non correspondant
    //   else if (!demande.getStatut().equals(valeur)) return false;
    // =========================================================================

    @Nested
    @DisplayName("L337 — correspondAuxFiltres() statut valeur unique non correspondant")
    class CorrespondAuxFiltres_L337 {

        @Test
        @DisplayName("L337 : demande EN_ATTENTE exclue par filtre statut=VERSEE (valeur unique)")
        void correspondAuxFiltres_statutValeurUniqueNonCorrespondant_retourneFalse_L337() {
            DemandeAide entityEnAttente = new DemandeAide();
            entityEnAttente.setId(randomId());

            DemandeAideResponse responseEnAttente = buildMinimalResponse(entityEnAttente.getId(), randomId());
            responseEnAttente.setTypeAide(TypeAide.TRANSPORT);
            responseEnAttente.setStatut(StatutAide.EN_ATTENTE);
            responseEnAttente.setPriorite(PrioriteAide.NORMALE);
            responseEnAttente.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entityEnAttente));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(responseEnAttente);

            // Filtre par statut = VERSEE (valeur unique) → demande EN_ATTENTE exclue
            Map<String, Object> filtres = Map.of("statut", StatutAide.VERSEE);

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("L337 : demande EN_ATTENTE exclue par filtre statut=VERSEE")
                    .isEmpty();
        }

        @Test
        @DisplayName("L337 branche false : demande VERSEE incluse par filtre statut=VERSEE")
        void correspondAuxFiltres_statutCorrespondant_inclutDemande_L337_false() {
            DemandeAide entityVersee = new DemandeAide();
            entityVersee.setId(randomId());

            DemandeAideResponse responseVersee = buildMinimalResponse(entityVersee.getId(), randomId());
            responseVersee.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
            responseVersee.setStatut(StatutAide.VERSEE);
            responseVersee.setPriorite(PrioriteAide.NORMALE);
            responseVersee.setAssociationId(randomId());

            when(demandeAideRepository.listAll()).thenReturn(List.of(entityVersee));
            when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(responseVersee);

            Map<String, Object> filtres = Map.of("statut", StatutAide.VERSEE);

            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtres);

            assertThat(results)
                    .as("Demande VERSEE incluse par filtre statut=VERSEE")
                    .hasSize(1);
        }
    }

    // =========================================================================
    // L365 — comparerParPriorite() — scores différents → return comparaisonScore
    //   if (comparaisonScore != 0) return comparaisonScore;
    // =========================================================================

    @Nested
    @DisplayName("L365 — comparerParPriorite() — scores différents (comparaisonScore != 0)")
    class ComparerParPriorite_L365 {

        @Test
        @DisplayName("L365 : deux demandes avec scorePriorite différents — triées par score croissant")
        void comparerParPriorite_scoresDifferents_triéCorrectement_L365() {
            UUID id1 = randomId();
            UUID id2 = randomId();

            DemandeAide entity1 = new DemandeAide();
            entity1.setId(id1);
            DemandeAide entity2 = new DemandeAide();
            entity2.setId(id2);

            DemandeAideResponse response1 = buildMinimalResponse(id1, randomId());
            response1.setScorePriorite(1.0);

            DemandeAideResponse response2 = buildMinimalResponse(id2, randomId());
            response2.setScorePriorite(5.0);

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity2, entity1));
            when(demandeAideMapper.toDTO(eq(entity1))).thenReturn(response1);
            when(demandeAideMapper.toDTO(eq(entity2))).thenReturn(response2);

            Map<String, Object> filtresVides = Map.of();
            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtresVides);

            assertThat(results).hasSize(2);
            assertThat(results.get(0).getScorePriorite())
                    .as("L365 : demande avec score 1.0 doit être en premier")
                    .isEqualTo(1.0);
            assertThat(results.get(1).getScorePriorite())
                    .as("L365 : demande avec score 5.0 doit être en second")
                    .isEqualTo(5.0);
        }

        @Test
        @DisplayName("L365 branche false : deux demandes avec même score — tri par dateCreation")
        void comparerParPriorite_memesScores_triéParDateCreation_L365_brancheFalse() {
            UUID id1 = randomId();
            UUID id2 = randomId();

            DemandeAide entity1 = new DemandeAide();
            entity1.setId(id1);
            DemandeAide entity2 = new DemandeAide();
            entity2.setId(id2);

            LocalDateTime date1 = LocalDateTime.now().minusDays(2);
            LocalDateTime date2 = LocalDateTime.now().minusDays(1);

            DemandeAideResponse response1 = buildMinimalResponse(id1, randomId());
            response1.setScorePriorite(3.0);
            response1.setDateCreation(date1);

            DemandeAideResponse response2 = buildMinimalResponse(id2, randomId());
            response2.setScorePriorite(3.0);
            response2.setDateCreation(date2);

            when(demandeAideRepository.listAll()).thenReturn(List.of(entity2, entity1));
            when(demandeAideMapper.toDTO(eq(entity1))).thenReturn(response1);
            when(demandeAideMapper.toDTO(eq(entity2))).thenReturn(response2);

            Map<String, Object> filtresVides = Map.of();
            List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(filtresVides);

            assertThat(results).hasSize(2);
            assertThat(results.get(0).getDateCreation())
                    .as("Scores égaux : plus ancienne date doit être en premier")
                    .isEqualTo(date1);
        }
    }

    // =========================================================================
    // L274 : obtenirDemandesEnRetard — associationId null → condition false (first operand)
    // =========================================================================

    @Test
    @DisplayName("obtenirDemandesEnRetard: associationId demande null → filtre L274 false (exclut)")
    void obtenirDemandesEnRetard_associationIdNull_filtreExclut() {
        UUID orgId = UUID.randomUUID();

        DemandeAide entity = new DemandeAide();
        entity.setId(randomId());

        DemandeAideResponse demande = buildMinimalResponse(randomId(), null);
        demande.setAssociationId(null); // null → L274: getAssociationId() != null = false → exclut

        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(entity)).thenReturn(demande);

        List<DemandeAideResponse> results = demandeAideService.obtenirDemandesEnRetard(orgId);

        assertThat(results).isEmpty(); // demande avec associationId null exclue par filtre
    }

    @Test
    @DisplayName("obtenirDemandesEnRetard: associationId non-null mais différent orgId → filtre L274 false (equals false)")
    void obtenirDemandesEnRetard_associationIdDifferent_filtreExclut() {
        UUID orgId = UUID.randomUUID();

        DemandeAide entity = new DemandeAide();
        entity.setId(randomId());

        DemandeAideResponse demande = buildMinimalResponse(randomId(), null);
        demande.setAssociationId(UUID.randomUUID()); // non-null mais différent → equals false → exclut

        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(entity)).thenReturn(demande);

        List<DemandeAideResponse> results = demandeAideService.obtenirDemandesEnRetard(orgId);

        assertThat(results).isEmpty();
    }
}
