package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.entity.Membre;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests d'intégration pour {@link MembreSuiviService}.
 * Couvre toutes les méthodes publiques : follow, unfollow, isFollowing, getFollowedIds.
 */
@QuarkusTest
class MembreSuiviServiceTest {

    @Inject
    MembreSuiviService membreSuiviService;

    @Inject
    MembreService membreService;

    /** Email de référence pour le follower créé dans chaque test. */
    private String followerEmail;
    private UUID suiviId;

    @BeforeEach
    void setup() {
        // Chaque test crée ses propres membres pour l'isolation
        followerEmail = "follower-" + UUID.randomUUID() + "@test.com";
        Membre follower = new Membre();
        follower.setPrenom("Follower");
        follower.setNom("Test");
        follower.setEmail(followerEmail);
        follower.setNumeroMembre("F-" + UUID.randomUUID().toString().substring(0, 8));
        follower.setDateNaissance(LocalDate.of(1990, 1, 1));
        membreService.creerMembre(follower);

        String suiviEmail = "suivi-" + UUID.randomUUID() + "@test.com";
        Membre suivi = new Membre();
        suivi.setPrenom("Suivi");
        suivi.setNom("Test");
        suivi.setEmail(suiviEmail);
        suivi.setNumeroMembre("S-" + UUID.randomUUID().toString().substring(0, 8));
        suivi.setDateNaissance(LocalDate.of(1992, 6, 15));
        membreService.creerMembre(suivi);
        suiviId = suivi.getId();
    }

    // === follow ===

    @Test
    @TestTransaction
    @DisplayName("follow crée un lien suivi et retourne true")
    void follow_createsLink_returnsTrue() {
        boolean result = membreSuiviService.follow(followerEmail, suiviId);

        assertThat(result).isTrue();
        assertThat(membreSuiviService.isFollowing(followerEmail, suiviId)).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("follow est idempotent : déjà suivi retourne true sans doublon")
    void follow_alreadyFollowing_returnsTrueIdempotent() {
        membreSuiviService.follow(followerEmail, suiviId);
        boolean result = membreSuiviService.follow(followerEmail, suiviId);

        assertThat(result).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("follow lève IllegalArgumentException si le membre connecté est introuvable")
    void follow_unknownFollower_throwsIllegalArgument() {
        assertThatThrownBy(() -> membreSuiviService.follow("inconnu@test.com", suiviId))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("Membre connecté introuvable");
    }

    @Test
    @TestTransaction
    @DisplayName("follow lève IllegalArgumentException si le membre cible est introuvable")
    void follow_unknownSuivi_throwsIllegalArgument() {
        UUID unknownId = UUID.randomUUID();
        assertThatThrownBy(() -> membreSuiviService.follow(followerEmail, unknownId))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("Membre cible introuvable");
    }

    @Test
    @TestTransaction
    @DisplayName("follow lève IllegalArgumentException si on essaie de se suivre soi-même")
    void follow_selfFollow_throwsIllegalArgument() {
        // Récupérer l'ID du follower
        Membre followerMembre = membreService.trouverParEmail(followerEmail).orElseThrow();

        assertThatThrownBy(() -> membreSuiviService.follow(followerEmail, followerMembre.getId()))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("Impossible de se suivre soi-même");
    }

    // === unfollow ===

    @Test
    @TestTransaction
    @DisplayName("unfollow supprime le lien suivi et retourne false")
    void unfollow_removesLink_returnsFalse() {
        membreSuiviService.follow(followerEmail, suiviId);
        assertThat(membreSuiviService.isFollowing(followerEmail, suiviId)).isTrue();

        boolean result = membreSuiviService.unfollow(followerEmail, suiviId);

        assertThat(result).isFalse();
        assertThat(membreSuiviService.isFollowing(followerEmail, suiviId)).isFalse();
    }

    @Test
    @TestTransaction
    @DisplayName("unfollow sur un non-suivi retourne false sans erreur")
    void unfollow_notFollowing_returnsFalseNoError() {
        boolean result = membreSuiviService.unfollow(followerEmail, suiviId);

        assertThat(result).isFalse();
    }

    @Test
    @TestTransaction
    @DisplayName("unfollow lève IllegalArgumentException si le membre connecté est introuvable")
    void unfollow_unknownFollower_throwsIllegalArgument() {
        assertThatThrownBy(() -> membreSuiviService.unfollow("inconnu@test.com", suiviId))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("Membre connecté introuvable");
    }

    // === isFollowing ===

    @Test
    @TestTransaction
    @DisplayName("isFollowing retourne false si le follower est introuvable")
    void isFollowing_unknownFollower_returnsFalse() {
        boolean result = membreSuiviService.isFollowing("fantome@test.com", suiviId);

        assertThat(result).isFalse();
    }

    @Test
    @TestTransaction
    @DisplayName("isFollowing retourne false si le lien n'existe pas")
    void isFollowing_noLink_returnsFalse() {
        boolean result = membreSuiviService.isFollowing(followerEmail, suiviId);

        assertThat(result).isFalse();
    }

    @Test
    @TestTransaction
    @DisplayName("isFollowing retourne true après un follow")
    void isFollowing_afterFollow_returnsTrue() {
        membreSuiviService.follow(followerEmail, suiviId);

        assertThat(membreSuiviService.isFollowing(followerEmail, suiviId)).isTrue();
    }

    // === getFollowedIds ===

    @Test
    @TestTransaction
    @DisplayName("getFollowedIds retourne une liste vide si le membre est introuvable")
    void getFollowedIds_unknownFollower_returnsEmptyList() {
        List<UUID> ids = membreSuiviService.getFollowedIds("fantome@test.com");

        assertThat(ids).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("getFollowedIds retourne une liste vide si aucun suivi")
    void getFollowedIds_noFollowing_returnsEmptyList() {
        List<UUID> ids = membreSuiviService.getFollowedIds(followerEmail);

        assertThat(ids).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("getFollowedIds retourne les IDs des membres suivis")
    void getFollowedIds_afterFollow_returnsIds() {
        membreSuiviService.follow(followerEmail, suiviId);

        List<UUID> ids = membreSuiviService.getFollowedIds(followerEmail);

        assertThat(ids).containsExactly(suiviId);
    }

    @Test
    @TestTransaction
    @DisplayName("getFollowedIds retourne plusieurs IDs si le membre suit plusieurs personnes")
    void getFollowedIds_multipleFollows_returnsAllIds() {
        // Créer un deuxième membre à suivre
        String email2 = "suivi2-" + UUID.randomUUID() + "@test.com";
        Membre suivi2 = new Membre();
        suivi2.setPrenom("Suivi2");
        suivi2.setNom("Test");
        suivi2.setEmail(email2);
        suivi2.setNumeroMembre("S2-" + UUID.randomUUID().toString().substring(0, 7));
        suivi2.setDateNaissance(LocalDate.of(1995, 3, 20));
        membreService.creerMembre(suivi2);

        membreSuiviService.follow(followerEmail, suiviId);
        membreSuiviService.follow(followerEmail, suivi2.getId());

        List<UUID> ids = membreSuiviService.getFollowedIds(followerEmail);

        assertThat(ids).hasSize(2);
        assertThat(ids).containsExactlyInAnyOrder(suiviId, suivi2.getId());
    }
}
