package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.MembreRole;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Role;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;

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 d'intégration pour {@link MembreService#promouvoirAdminOrganisation(UUID)} avec une
 * vraie base de données, afin de couvrir le bloc {@code forEach} (ligne 210) et
 * {@code entityManager.persist(mr)} qui nécessite une entité gérée par JPA.
 *
 * <p>Ces tests complètent {@link MembreServiceMissingCoverageTest} qui ne peut pas couvrir
 * ces chemins avec des mocks CDI.
 */
@QuarkusTest
@DisplayName("MembreService — promouvoirAdminOrganisation avec vraie DB (couvre L210 entityManager.persist)")
class MembreServicePromouvoirRealDBTest {

    @Inject
    MembreService membreService;

    @Inject
    EntityManager em;

    // ─── Helpers ─────────────────────────────────────────────────────────────

    private Membre creerMembre(String email) {
        Membre m = new Membre();
        m.setNumeroMembre("UF-PROM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
        m.setPrenom("Promu");
        m.setNom("Test");
        m.setEmail(email);
        m.setDateNaissance(LocalDate.of(1985, 3, 10));
        m.setStatutCompte("EN_ATTENTE_VALIDATION");
        m.setActif(false);
        em.persist(m);
        em.flush();
        return m;
    }

    private Organisation creerOrg() {
        Organisation org = new Organisation();
        org.setNom("Org Promu Real " + UUID.randomUUID().toString().substring(0, 8));
        org.setTypeOrganisation("ASSOCIATION");
        org.setStatut("ACTIF");
        org.setEmail("org.promu." + UUID.randomUUID() + "@test.com");
        org.setDateCreation(LocalDateTime.now());
        org.setActif(true);
        em.persist(org);
        em.flush();
        return org;
    }

    private MembreOrganisation creerLien(Membre membre, Organisation org) {
        MembreOrganisation mo = new MembreOrganisation();
        mo.setMembre(membre);
        mo.setOrganisation(org);
        mo.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);
        mo.setDateAdhesion(LocalDate.now());
        mo.setActif(true);
        em.persist(mo);
        em.flush();
        return mo;
    }

    private Role creerOuTrouverRole(String code, String libelle) {
        return em.createQuery("SELECT r FROM Role r WHERE r.code = :code", Role.class)
                .setParameter("code", code)
                .getResultStream()
                .findFirst()
                .orElseGet(() -> {
                    Role r = new Role();
                    r.setCode(code);
                    r.setLibelle(libelle);
                    r.setActif(true);
                    em.persist(r);
                    em.flush();
                    return r;
                });
    }

    private MembreRole creerRole(MembreOrganisation mo, Organisation org, String roleCode) {
        Role role = creerOuTrouverRole(roleCode, roleCode + " Test");

        MembreRole mr = new MembreRole();
        mr.setMembreOrganisation(mo);
        mr.setOrganisation(org);
        mr.setRole(role);
        mr.setActif(true);
        mr.setDateDebut(LocalDate.now());
        em.persist(mr);
        em.flush();
        return mr;
    }

    // ─── Tests ───────────────────────────────────────────────────────────────

    /**
     * Couvre {@code lambda$promouvoirAdminOrganisation$3} (ligne 210 entityManager.persist(mr))
     * ET {@code lambda$assignerRoleDefaut$23} (lignes 1180-1189) :
     *
     * <p>Le forEach sur les rôles actifs désactive les rôles (mr.setActif(false))
     * et les persiste via entityManager.persist(mr) avec une entité JPA managée.
     * Ensuite assignerRoleDefaut("ORGADMIN") est appelé avec le rôle ORGADMIN créé dans la DB.
     */
    @Test
    @TestTransaction
    @DisplayName("promouvoirAdminOrganisation — forEach + assignerRoleDefaut avec entités réelles (couvre L210 persist + L1180-1189 lambda)")
    void promouvoirAdminOrganisation_avecRolesActifsReels_forEach_assignerRoleDefaut() {
        // Créer des entités réelles dans la DB (transaction de test → rollback automatique)
        // Créer d'abord les rôles nécessaires (Flyway désactivé en test)
        creerOuTrouverRole("ORGADMIN", "Administrateur Organisation Test");

        Membre membre = creerMembre("promu.real." + UUID.randomUUID() + "@test.dev");
        Organisation org = creerOrg();
        MembreOrganisation mo = creerLien(membre, org);
        // Crée un MembreRole actif pour le membre (role SIMPLEMEMBER)
        creerRole(mo, org, "SIMPLEMEMBER");

        // Appeler la méthode sous test
        // → forEach sur [SIMPLEMEMBER role] → mr.setActif(false) + entityManager.persist(mr)
        // → assignerRoleDefaut("ORGADMIN") → roleRepository.findByCode("ORGADMIN") = présent
        // → lambda ifPresent : crée nouveau MembreRole ORGADMIN et le persiste
        Membre result = membreService.promouvoirAdminOrganisation(membre.getId());

        // Le membre doit être actif et promu
        assertThat(result.getStatutCompte()).isEqualTo("ACTIF");
        assertThat(result.getActif()).isTrue();

        // Vérifier qu'un MembreRole ORGADMIN a été créé en base
        List<MembreRole> rolesActifs = em.createQuery(
                "SELECT mr FROM MembreRole mr WHERE mr.membreOrganisation.membre.id = :membreId AND mr.actif = true",
                MembreRole.class)
                .setParameter("membreId", membre.getId())
                .getResultList();
        // L'ORGADMIN role doit être présent (actif=true, créé par assignerRoleDefaut)
        assertThat(rolesActifs.stream().anyMatch(mr -> "ORGADMIN".equals(mr.getRole().getCode()))).isTrue();
    }

    /**
     * Couvre {@code promouvoirAdminOrganisation} sans lien existant → ifPresent non exécuté.
     */
    @Test
    @TestTransaction
    @DisplayName("promouvoirAdminOrganisation — sans lien existant → assignerRoleDefaut non appelé → 200 OK")
    void promouvoirAdminOrganisation_sansLienExistant_promuSansAssigner() {
        Membre membre = creerMembre("promu.nolien." + UUID.randomUUID() + "@test.dev");

        // Pas de MembreOrganisation → membreOrganisationRepository.findFirstByMembreId retourne empty
        // → ifPresent non exécuté → assignerRoleDefaut non appelé

        Membre result = membreService.promouvoirAdminOrganisation(membre.getId());

        assertThat(result.getStatutCompte()).isEqualTo("ACTIF");
        assertThat(result.getActif()).isTrue();
    }

    /**
     * Couvre {@code affecterOrganisation} lignes 178-181 (happy path) :
     * Le membre n'est pas encore lié, donc la méthode crée le lien via
     * {@code lierMembreOrganisationEtIncrementerQuota}.
     *
     * <p>Ce test couvre les lignes précédemment manquantes de {@code affecterOrganisation}.
     */
    @Test
    @TestTransaction
    @DisplayName("affecterOrganisation — membre non lié → crée lien via lierMembreOrganisation (couvre L178-181)")
    void affecterOrganisation_nonLie_creeLienEtRetourneMembre() {
        Membre membre = creerMembre("affect.nonlie." + UUID.randomUUID() + "@test.dev");
        Organisation org = creerOrg();

        // Le membre n'est pas lié → dejaLie = false → appel lierMembreOrganisationEtIncrementerQuota
        Membre result = membreService.affecterOrganisation(membre.getId(), org.getId());

        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(membre.getId());

        // Vérifier que le lien MembreOrganisation a été créé
        List<MembreOrganisation> liens = em.createQuery(
                "SELECT mo FROM MembreOrganisation mo WHERE mo.membre.id = :membreId AND mo.organisation.id = :orgId",
                MembreOrganisation.class)
                .setParameter("membreId", membre.getId())
                .setParameter("orgId", org.getId())
                .getResultList();
        assertThat(liens).hasSize(1);
        assertThat(liens.get(0).getStatutMembre()).isEqualTo(StatutMembre.EN_ATTENTE_VALIDATION);
    }
}
