package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO;
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.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.mapper.DemandeAideMapper;
import io.quarkus.test.InjectMock;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

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

/**
 * Tests ciblant les branches L99 et L214 de {@link DemandeAideService}.
 *
 * <p>Ces branches nécessitent de contrôler ce que retourne {@link DemandeAideMapper#toDTO},
 * tout en utilisant les vrais repositories pour éviter les NPE causés par le champ
 * {@code entityManager} null dans les spies Quarkus {@code @InjectMock} sur les dépôts
 * qui surchargent {@code findById(UUID)}.
 *
 * <p>Stratégie : {@code @InjectMock} uniquement sur le mapper, repos réels via
 * {@code @TestTransaction} + entités persistées manuellement.
 *
 * <ul>
 *   <li><b>L99</b> — {@code creerDemande()} : ternaire
 *       {@code response.getMembreDemandeurId() != null ? ... : null} — branche false.</li>
 *   <li><b>L214</b> — {@code changerStatut()} : ternaire
 *       {@code response.getHistoriqueStatuts() != null} — branche true.</li>
 * </ul>
 */
@QuarkusTest
@DisplayName("DemandeAideService — branches L99 et L214 (vrais repositories)")
class DemandeAideServiceL99L214Test {

    @Inject
    DemandeAideService demandeAideService;

    /** Seul mock : le mapper, pour contrôler ce que retourne toDTO() */
    @InjectMock
    DemandeAideMapper demandeAideMapper;

    @Inject
    EntityManager entityManager;

    // =========================================================================
    // Helpers — persistance d'entités minimales valides
    // =========================================================================

    private Membre creerMembre() {
        Membre m = Membre.builder()
                .numeroMembre("UF-L99-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase())
                .prenom("Test")
                .nom("L99")
                .email("l99." + UUID.randomUUID() + "@test.com")
                .dateNaissance(LocalDate.of(1990, 1, 1))
                .build();
        m.setDateCreation(LocalDateTime.now());
        m.setActif(true);
        m.setStatutCompte("ACTIF");
        entityManager.persist(m);
        entityManager.flush();
        return m;
    }

    private Organisation creerOrganisation() {
        Organisation org = Organisation.builder()
                .nom("Org L99 " + UUID.randomUUID().toString().substring(0, 8))
                .email("org.l99." + UUID.randomUUID() + "@test.com")
                .typeOrganisation("ASSOCIATION")
                .statut("ACTIF")
                .build();
        org.setDateCreation(LocalDateTime.now());
        org.setActif(true);
        entityManager.persist(org);
        entityManager.flush();
        return org;
    }

    // =========================================================================
    // L99 — creerDemande() — ternaire false :
    //   auteurId(response.getMembreDemandeurId() != null ? ... : null)
    //
    // Le mapper mock retourne un DTO avec membreDemandeurId = null.
    // → auteurId du HistoriqueStatutDTO initial est null (branche false L99).
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("L99 branche false : auteurId=null dans l'historique initial quand mapper retourne membreDemandeurId=null")
    void creerDemande_mapperRetourneMembreDemandeurIdNull_auteurIdNullDansHistorique_L99() {
        Membre membre = creerMembre();
        Organisation org = creerOrganisation();

        // L'entité retournée par le mapper mock doit être persistable : on lui donne
        // le vrai demandeur et la vraie organisation pour satisfaire les contraintes NOT NULL.
        DemandeAide entityMockRetour = DemandeAide.builder()
                .titre("Demande L99")
                .description("Desc L99")
                .typeAide(TypeAide.AIDE_ALIMENTAIRE)
                .statut(StatutAide.EN_ATTENTE)
                .demandeur(membre)
                .organisation(org)
                .build();
        when(demandeAideMapper.toEntity(any(), any(), any(), any())).thenReturn(entityMockRetour);

        // toDTO retourne un DTO SANS membreDemandeurId (null)
        // → condition L99 : response.getMembreDemandeurId() != null → false
        // → auteurId = null dans le HistoriqueStatutDTO initial
        DemandeAideResponse dtoNull = new DemandeAideResponse();
        dtoNull.setId(UUID.randomUUID());
        dtoNull.setStatut(StatutAide.EN_ATTENTE);
        dtoNull.setPriorite(PrioriteAide.NORMALE);
        dtoNull.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        dtoNull.setMontantDemande(new BigDecimal("200.00"));
        dtoNull.setDateCreation(LocalDateTime.now());
        dtoNull.setMembreDemandeurId(null); // ← null → L99 branche false → auteurId = null
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(dtoNull);

        dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest request =
                dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest.builder()
                        .titre("Demande L99")
                        .description("Description L99")
                        .typeAide(TypeAide.AIDE_ALIMENTAIRE)
                        .priorite(PrioriteAide.NORMALE)
                        .membreDemandeurId(membre.getId())
                        .associationId(org.getId())
                        .build();

        DemandeAideResponse result = demandeAideService.creerDemande(request);

        assertThat(result).isNotNull();
        // Le service crée un HistoriqueStatutDTO initial avec auteurId = null (L99 branche false)
        assertThat(result.getHistoriqueStatuts())
                .as("Un historique initial doit être présent")
                .isNotNull()
                .hasSize(1);
        assertThat(result.getHistoriqueStatuts().get(0).getAuteurId())
                .as("L99 branche false : auteurId doit être null quand membreDemandeurId est null")
                .isNull();
    }

    // =========================================================================
    // L214 — changerStatut() — branche true :
    //   response.getHistoriqueStatuts() != null → utilise la liste existante
    //
    // Le mapper mock retourne un DTO avec historiqueStatuts déjà renseigné (non-null).
    // → l'historique existant est combiné avec le nouveau → L214 branche true.
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("L214 branche true : historique existant conservé quand mapper retourne historiqueStatuts non-null")
    void changerStatut_mapperRetourneHistoriqueNonNull_historiqueCombine_L214() {
        Membre membre = creerMembre();
        Organisation org = creerOrganisation();

        // Persister une vraie DemandeAide (statut EN_ATTENTE → transition vers EN_COURS_EVALUATION valide)
        DemandeAide demande = DemandeAide.builder()
                .titre("Demande L214")
                .description("Desc L214")
                .typeAide(TypeAide.AIDE_ALIMENTAIRE)
                .statut(StatutAide.EN_ATTENTE)
                .demandeur(membre)
                .organisation(org)
                .build();
        entityManager.persist(demande);
        entityManager.flush();
        UUID demandeId = demande.getId();

        // Le mapper retourne un DTO avec historiqueStatuts DÉJÀ NON-NULL
        // → L214 branche true : la liste existante est utilisée + le nouvel historique s'y ajoute
        HistoriqueStatutDTO historiqueExistant = HistoriqueStatutDTO.builder()
                .id(UUID.randomUUID().toString())
                .ancienStatut(null)
                .nouveauStatut(StatutAide.EN_ATTENTE)
                .dateChangement(LocalDateTime.now().minusHours(1))
                .motif("Création initiale")
                .estAutomatique(true)
                .build();

        DemandeAideResponse dtoAvecHistorique = new DemandeAideResponse();
        dtoAvecHistorique.setId(demandeId);
        dtoAvecHistorique.setStatut(StatutAide.EN_COURS_EVALUATION);
        dtoAvecHistorique.setPriorite(PrioriteAide.NORMALE);
        dtoAvecHistorique.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        dtoAvecHistorique.setMontantDemande(new BigDecimal("100.00"));
        dtoAvecHistorique.setDateCreation(LocalDateTime.now().minusDays(1));
        dtoAvecHistorique.setMembreDemandeurId(membre.getId());
        dtoAvecHistorique.setHistoriqueStatuts(List.of(historiqueExistant)); // ← non-null → L214 true
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(dtoAvecHistorique);

        DemandeAideResponse result = demandeAideService.changerStatut(
                demandeId, StatutAide.EN_COURS_EVALUATION, "Évaluation débutée");

        // L214 branche true : 1 historique existant + 1 nouvel historique = 2 entrées
        assertThat(result.getHistoriqueStatuts())
                .as("L214 branche true : historique existant (1) + nouveau (1) = 2 entrées")
                .hasSize(2);
        assertThat(result.getHistoriqueStatuts().get(0).getMotif())
                .isEqualTo("Création initiale");
        assertThat(result.getHistoriqueStatuts().get(1).getNouveauStatut())
                .isEqualTo(StatutAide.EN_COURS_EVALUATION);
    }
}
