package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.api.enums.communication.ConversationType;
import dev.lions.unionflow.server.api.enums.communication.MessagePriority;
import dev.lions.unionflow.server.api.enums.communication.MessageStatus;
import dev.lions.unionflow.server.api.enums.communication.MessageType;
import dev.lions.unionflow.server.entity.Conversation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Message;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

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

@QuarkusTest
@DisplayName("MessageRepository — tests des méthodes de requête")
class MessageRepositoryTest {

    @Inject
    MessageRepository messageRepository;

    @Inject
    EntityManager em;

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

    private Membre persistMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("MSG-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
        m.setPrenom("Jean");
        m.setNom("Messagerie");
        m.setEmail("msg." + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setActif(true);
        em.persist(m);
        em.flush();
        return m;
    }

    private Conversation persistConversation() {
        Conversation conv = new Conversation();
        conv.setName("Conv Test " + UUID.randomUUID().toString().substring(0, 8));
        conv.setType(ConversationType.GROUP);
        conv.setActif(true);
        em.persist(conv);
        em.flush();
        return conv;
    }

    private Message persistMessage(Conversation conv, Membre sender, MessageStatus status) {
        Message msg = new Message();
        msg.setConversation(conv);
        msg.setSender(sender);
        msg.setSenderName(sender.getPrenom() + " " + sender.getNom());
        msg.setContent("Contenu test " + UUID.randomUUID());
        msg.setType(MessageType.INDIVIDUAL);
        msg.setStatus(status);
        msg.setPriority(MessagePriority.NORMAL);
        msg.setIsEdited(false);
        msg.setIsDeleted(false);
        msg.setActif(true);
        em.persist(msg);
        em.flush();
        return msg;
    }

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

    @Test
    @TestTransaction
    @DisplayName("findByConversation retourne les messages actifs non supprimés")
    void findByConversation_retourneMessagesActifs() {
        Membre sender = persistMembre();
        Conversation conv = persistConversation();
        persistMessage(conv, sender, MessageStatus.SENT);
        persistMessage(conv, sender, MessageStatus.READ);

        List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);

        assertThat(messages).isNotNull();
        assertThat(messages).hasSizeGreaterThanOrEqualTo(2);
        messages.forEach(m -> assertThat(m.getIsDeleted()).isFalse());
    }

    @Test
    @TestTransaction
    @DisplayName("findByConversation respecte la limite de pagination")
    void findByConversation_respecteLimite() {
        Membre sender = persistMembre();
        Conversation conv = persistConversation();
        for (int i = 0; i < 5; i++) {
            persistMessage(conv, sender, MessageStatus.SENT);
        }

        List<Message> messages = messageRepository.findByConversation(conv.getId(), 3);

        assertThat(messages).hasSizeLessThanOrEqualTo(3);
    }

    @Test
    @TestTransaction
    @DisplayName("findByConversation retourne liste vide pour conversation sans messages")
    void findByConversation_conversationVide_retourneListe() {
        Conversation conv = persistConversation();

        List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);

        assertThat(messages).isNotNull();
        assertThat(messages).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByConversation exclut les messages supprimés")
    void findByConversation_exclutMessagesSupprimés() {
        Membre sender = persistMembre();
        Conversation conv = persistConversation();
        // Message supprimé
        Message msgSupprime = persistMessage(conv, sender, MessageStatus.SENT);
        msgSupprime.setIsDeleted(true);
        em.flush();

        List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);

        assertThat(messages).noneMatch(m -> m.getId().equals(msgSupprime.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("countUnreadByConversationAndMember compte les messages SENT et DELIVERED d'autres membres")
    void countUnreadByConversationAndMember_compteMsgNonLusAutresMembres() {
        Membre sender = persistMembre();
        Membre reader = persistMembre();
        Conversation conv = persistConversation();
        persistMessage(conv, sender, MessageStatus.SENT);
        persistMessage(conv, sender, MessageStatus.DELIVERED);
        persistMessage(conv, sender, MessageStatus.READ); // déjà lu → exclu

        long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), reader.getId());

        assertThat(count).isGreaterThanOrEqualTo(2);
    }

    @Test
    @TestTransaction
    @DisplayName("countUnreadByConversationAndMember exclut les messages du membre lui-même")
    void countUnreadByConversationAndMember_excluMessagesPropresMembre() {
        Membre sender = persistMembre();
        Conversation conv = persistConversation();
        persistMessage(conv, sender, MessageStatus.SENT);

        // Le sender lui-même : ses propres messages ne sont pas comptés comme non lus
        long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), sender.getId());

        assertThat(count).isEqualTo(0);
    }

    @Test
    @TestTransaction
    @DisplayName("countUnreadByConversationAndMember retourne 0 pour conversation vide")
    void countUnreadByConversationAndMember_conversationVide_retourneZero() {
        Conversation conv = persistConversation();
        UUID membreId = UUID.randomUUID();

        long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), membreId);

        assertThat(count).isEqualTo(0);
    }

    @Test
    @TestTransaction
    @DisplayName("markAllAsReadByConversationAndMember marque les messages SENT et DELIVERED en READ")
    void markAllAsReadByConversationAndMember_marqueLesMessagesEnRead() {
        Membre sender = persistMembre();
        Membre reader = persistMembre();
        Conversation conv = persistConversation();
        persistMessage(conv, sender, MessageStatus.SENT);
        persistMessage(conv, sender, MessageStatus.DELIVERED);

        int updated = messageRepository.markAllAsReadByConversationAndMember(conv.getId(), reader.getId());

        assertThat(updated).isGreaterThanOrEqualTo(2);
    }

    @Test
    @TestTransaction
    @DisplayName("markAllAsReadByConversationAndMember ne touche pas les messages déjà READ")
    void markAllAsReadByConversationAndMember_neTouchePasMsgDejaRead() {
        Membre sender = persistMembre();
        Membre reader = persistMembre();
        Conversation conv = persistConversation();
        persistMessage(conv, sender, MessageStatus.READ); // déjà lu

        int updated = messageRepository.markAllAsReadByConversationAndMember(conv.getId(), reader.getId());

        assertThat(updated).isEqualTo(0);
    }

    @Test
    @TestTransaction
    @DisplayName("findLastByConversation retourne le dernier message")
    void findLastByConversation_retourneDernierMessage() {
        Membre sender = persistMembre();
        Conversation conv = persistConversation();
        // Set explicit dateCreation to guarantee ordering (PrePersist skips if non-null)
        Message premierMsg = new Message();
        premierMsg.setConversation(conv);
        premierMsg.setSender(sender);
        premierMsg.setSenderName(sender.getPrenom() + " " + sender.getNom());
        premierMsg.setContent("Premier message");
        premierMsg.setType(dev.lions.unionflow.server.api.enums.communication.MessageType.INDIVIDUAL);
        premierMsg.setStatus(MessageStatus.SENT);
        premierMsg.setPriority(dev.lions.unionflow.server.api.enums.communication.MessagePriority.NORMAL);
        premierMsg.setIsEdited(false);
        premierMsg.setIsDeleted(false);
        premierMsg.setActif(true);
        premierMsg.setDateCreation(java.time.LocalDateTime.now().minusSeconds(10));
        em.persist(premierMsg);
        em.flush();

        Message dernierMsg = persistMessage(conv, sender, MessageStatus.DELIVERED);

        Message last = messageRepository.findLastByConversation(conv.getId());

        assertThat(last).isNotNull();
        assertThat(last.getId()).isEqualTo(dernierMsg.getId());
    }

    @Test
    @TestTransaction
    @DisplayName("findLastByConversation retourne null pour conversation vide")
    void findLastByConversation_conversationVide_retourneNull() {
        Conversation conv = persistConversation();

        Message last = messageRepository.findLastByConversation(conv.getId());

        assertThat(last).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findLastByConversation retourne null pour conversation inexistante")
    void findLastByConversation_conversationInexistante_retourneNull() {
        Message last = messageRepository.findLastByConversation(UUID.randomUUID());

        assertThat(last).isNull();
    }
}
