package dev.lions.unionflow.server.service.support;

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

import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.util.Set;
import java.util.UUID;

/**
 * Tests pour {@link SecuriteHelper}.
 *
 * <p>Branches couvertes :
 * <ul>
 *   <li>resolveEmail : identité nulle/anonyme → null</li>
 *   <li>resolveEmail : claim email absent → fallback sur principal.getName()</li>
 *   <li>resolveEmail : isAnonymous() = true → null</li>
 *   <li>resolveMembreId : membre non trouvé → NotFoundException</li>
 *   <li>resolveMembreId : membre trouvé (exact et fallback toLowerCase)</li>
 *   <li>resolveMembreId : membre actif=false → NotFoundException (filtre rejette)</li>
 *   <li>getRoles : avec et sans utilisateur</li>
 * </ul>
 */
@QuarkusTest
class SecuriteHelperTest {

    @Inject
    SecuriteHelper helper;

    @Inject
    MembreRepository membreRepository;

    private UUID createdMembreId;

    @BeforeEach
    @Transactional
    void setupMembre() {
        Membre m = new Membre();
        m.setPrenom("Upper");
        m.setNom("Case");
        m.setEmail("upper@unionflow.test");
        m.setNumeroMembre("M-UP-" + UUID.randomUUID().toString().substring(0, 8));
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setStatutCompte("ACTIF");
        m.setActif(true);
        membreRepository.persist(m);
        createdMembreId = m.getId();
    }

    @AfterEach
    @Transactional
    void cleanupMembre() {
        if (createdMembreId != null) {
            try {
                membreRepository.deleteById(createdMembreId);
            } catch (Exception ignore) {
            }
        }
    }

    // =========================================================================
    // resolveEmail
    // =========================================================================

    @Test
    @TestSecurity(user = "user@unionflow.test")
    @DisplayName("resolveEmail : utilisateur authentifié → retourne le nom du principal")
    void resolveEmail_withAuthenticatedUser_returnsEmail() {
        // @TestSecurity crée un principal dont getName() = "user@unionflow.test"
        // Le principal n'est pas un JsonWebToken → pas de claim "email" → fallback getName()
        String email = helper.resolveEmail();
        assertThat(email).isEqualTo("user@unionflow.test");
    }

    @Test
    @DisplayName("resolveEmail : aucun utilisateur connecté (anonyme) → retourne null")
    void resolveEmail_withoutUser_returnsNull() {
        // Sans @TestSecurity → securityIdentity.isAnonymous() = true → retourne null
        String email = helper.resolveEmail();
        assertThat(email).isNull();
    }

    @Test
    @TestSecurity(user = "user@unionflow.test")
    @DisplayName("resolveEmail : principal non-JWT → fallback sur getName() (branche 'else' après instanceof JsonWebToken)")
    void resolveEmail_nonJwtPrincipal_fallbackToName() {
        // @TestSecurity injecte un principal simple (pas JsonWebToken)
        // → branche 'instanceof JsonWebToken' non prise
        // → fallback: securityIdentity.getPrincipal().getName()
        String email = helper.resolveEmail();
        assertThat(email).isNotNull();
        assertThat(email).isEqualTo("user@unionflow.test");
    }

    // =========================================================================
    // resolveMembreId
    // =========================================================================

    @Test
    @DisplayName("resolveMembreId : aucun utilisateur → NotFoundException (email null)")
    void resolveMembreId_withoutUser_throwsNotFoundException() {
        // resolveEmail() retourne null → throw NotFoundException("Identité non disponible")
        assertThatThrownBy(() -> helper.resolveMembreId())
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Identité non disponible");
    }

    @Test
    @TestSecurity(user = "nonexistent@unionflow.test")
    @DisplayName("resolveMembreId : utilisateur connecté mais membre absent en DB → NotFoundException")
    void resolveMembreId_withNonExistentMembre_throwsNotFoundException() {
        // email = "nonexistent@unionflow.test" → aucun Membre en DB → orElseThrow NotFoundException
        assertThatThrownBy(() -> helper.resolveMembreId())
                .isInstanceOf(NotFoundException.class)
                .hasMessageContaining("Membre non trouvé");
    }

    @Test
    @TestSecurity(user = "upper@unionflow.test")
    @DisplayName("resolveMembreId : email exact en DB → retourne l'ID du membre")
    void resolveMembreId_withExactEmailMatch_returnsMembreId() {
        // findByEmail("upper@unionflow.test") trouve le membre directement
        UUID id = helper.resolveMembreId();
        assertThat(id).isEqualTo(createdMembreId);
    }

    @Test
    @TestSecurity(user = "UPPER@UNIONFLOW.TEST")
    @DisplayName("resolveMembreId : email en MAJUSCULES → fallback toLowerCase couvre la branche .or()")
    void resolveMembreId_withUpperCaseEmail_triggersLowercaseFallback() {
        // findByEmail("UPPER@UNIONFLOW.TEST") → vide
        // .or(() -> findByEmail("upper@unionflow.test")) → trouve le membre
        UUID id = helper.resolveMembreId();
        assertThat(id).isEqualTo(createdMembreId);
    }

    @Test
    @TestSecurity(user = "upper@unionflow.test")
    @DisplayName("resolveMembreId : membre avec actif=false → NotFoundException (filtre .filter() rejette)")
    @Transactional
    void resolveMembreId_membreActifFalse_throwsNotFoundException() {
        // Force actif=false pour que le filtre .filter(m -> m.getActif() == null || m.getActif()) rejette
        membreRepository.findByIdOptional(createdMembreId).ifPresent(m -> {
            m.setActif(false);
            membreRepository.persist(m);
        });
        assertThatThrownBy(() -> helper.resolveMembreId())
                .isInstanceOf(NotFoundException.class);
    }

    // =========================================================================
    // getRoles
    // =========================================================================

    @Test
    @DisplayName("getRoles : aucun utilisateur connecté → ensemble vide retourné")
    void getRoles_withoutUser_returnsEmptySet() {
        Set<String> roles = helper.getRoles();
        assertThat(roles).isNotNull().isEmpty();
    }

    @Test
    @TestSecurity(user = "admin@unionflow.test", roles = {"ADMIN", "SUPER_ADMIN"})
    @DisplayName("getRoles : utilisateur avec rôles → rôles retournés")
    void getRoles_withAuthenticatedUser_returnsRoles() {
        Set<String> roles = helper.getRoles();
        assertThat(roles).isNotNull().contains("ADMIN", "SUPER_ADMIN");
    }
}
