package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Notification;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.NotificationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class NotificationHistoryServiceTest {

    @Inject
    NotificationHistoryService notificationHistoryService;

    @InjectMock
    NotificationRepository notificationRepository;

    @InjectMock
    MembreRepository membreRepository;

    @Test
    @DisplayName("enregistrerNotification persiste une nouvelle notification")
    void enregistrerNotification_persistsNotification() {
        UUID userId = UUID.randomUUID();
        Membre membre = new Membre();
        membre.setId(userId);

        when(membreRepository.findByIdOptional(userId)).thenReturn(Optional.of(membre));

        notificationHistoryService.enregistrerNotification(
                userId, "TEST_TYPE", "Titre", "Message", "EMAIL", true);

        verify(notificationRepository).persist(any(Notification.class));
    }

    @Test
    @DisplayName("marquerCommeLue met à jour le statut et la date de lecture")
    void marquerCommeLue_updatesStatus() {
        UUID userId = UUID.randomUUID();
        UUID notifId = UUID.randomUUID();

        Membre membre = new Membre();
        membre.setId(userId);

        Notification notification = new Notification();
        notification.setId(notifId);
        notification.setMembre(membre);
        notification.setStatut("ENVOYEE");

        when(notificationRepository.findNotificationById(notifId)).thenReturn(Optional.of(notification));

        notificationHistoryService.marquerCommeLue(userId, notifId);

        assertThat(notification.getStatut()).isEqualTo("LUE");
        assertThat(notification.getDateLecture()).isNotNull();
        verify(notificationRepository).persist(notification);
    }

    @Test
    @DisplayName("nettoyerHistorique supprime les anciennes notifications")
    void nettoyerHistorique_deletesOldNotifications() {
        notificationHistoryService.nettoyerHistorique();
        verify(notificationRepository).delete(anyString(), any(Object[].class));
    }

    @Test
    @DisplayName("enregistrerNotification avec type et canal null utilise les valeurs par défaut IN_APP")
    void enregistrerNotification_nullTypeAndCanal_usesDefaults() {
        UUID userId = UUID.randomUUID();
        when(membreRepository.findByIdOptional(userId)).thenReturn(Optional.empty());

        notificationHistoryService.enregistrerNotification(userId, null, "Titre", "Corps", null, false);

        verify(notificationRepository).persist(any(Notification.class));
    }

    @Test
    @DisplayName("obtenirHistorique(uuid) délègue au repository")
    void obtenirHistorique_delegatesToRepository() {
        UUID userId = UUID.randomUUID();
        Notification n = new Notification();
        when(notificationRepository.findByMembreId(userId)).thenReturn(List.of(n));

        List<Notification> result = notificationHistoryService.obtenirHistorique(userId);

        assertThat(result).hasSize(1);
        verify(notificationRepository).findByMembreId(userId);
    }

    @Test
    @DisplayName("obtenirHistorique avec pagination retourne les résultats paginés")
    @SuppressWarnings("unchecked")
    void obtenirHistorique_withPagination_returnsList() {
        UUID userId = UUID.randomUUID();
        // La méthode appelle notificationRepository.find(query, userId).page(page, taille).list()
        // On mocke la chaîne PanacheQuery pour retourner une liste vide.
        io.quarkus.hibernate.orm.panache.PanacheQuery<Notification> mockPage =
            mock(io.quarkus.hibernate.orm.panache.PanacheQuery.class);
        when(mockPage.list()).thenReturn(List.of());

        io.quarkus.hibernate.orm.panache.PanacheQuery<Notification> mockQuery =
            mock(io.quarkus.hibernate.orm.panache.PanacheQuery.class);
        when(mockQuery.page(0, 10)).thenReturn(mockPage);

        when(notificationRepository.find(anyString(), any(Object[].class))).thenReturn(mockQuery);

        List<Notification> result = notificationHistoryService.obtenirHistorique(userId, 0, 10);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("marquerCommeLue ne modifie pas la notification si l'utilisateur ne correspond pas")
    void marquerCommeLue_wrongUser_doesNotUpdate() {
        UUID userId = UUID.randomUUID();
        UUID otherUserId = UUID.randomUUID();
        UUID notifId = UUID.randomUUID();

        Membre autreMembre = new Membre();
        autreMembre.setId(otherUserId);

        Notification notification = new Notification();
        notification.setId(notifId);
        notification.setMembre(autreMembre);
        notification.setStatut("ENVOYEE");

        when(notificationRepository.findNotificationById(notifId)).thenReturn(Optional.of(notification));

        notificationHistoryService.marquerCommeLue(userId, notifId);

        // Le statut ne doit PAS être mis à LUE car l'utilisateur ne correspond pas
        assertThat(notification.getStatut()).isEqualTo("ENVOYEE");
        verify(notificationRepository, never()).persist(notification);
    }

    @Test
    @DisplayName("marquerCommeLue ne fait rien si la notification n'existe pas")
    void marquerCommeLue_notifNotFound_doesNothing() {
        UUID userId = UUID.randomUUID();
        UUID notifId = UUID.randomUUID();
        when(notificationRepository.findNotificationById(notifId)).thenReturn(Optional.empty());

        notificationHistoryService.marquerCommeLue(userId, notifId);

        verify(notificationRepository, never()).persist(any(Notification.class));
    }

    @Test
    @DisplayName("marquerCommeLue ne modifie pas la notification si getMembre() est null (branche getMembre()==null L80)")
    void marquerCommeLue_nullMembre_doesNotUpdate() {
        UUID userId = UUID.randomUUID();
        UUID notifId = UUID.randomUUID();

        Notification notification = new Notification();
        notification.setId(notifId);
        notification.setMembre(null); // getMembre() == null → condition false → pas de mise à jour
        notification.setStatut("ENVOYEE");

        when(notificationRepository.findNotificationById(notifId)).thenReturn(Optional.of(notification));

        notificationHistoryService.marquerCommeLue(userId, notifId);

        // Le statut ne change pas car getMembre() est null
        assertThat(notification.getStatut()).isEqualTo("ENVOYEE");
        verify(notificationRepository, never()).persist(notification);
    }

    @Test
    @DisplayName("marquerToutesCommeLues met toutes les notifications non lues à LUE")
    void marquerToutesCommeLues_updatesAllUnread() {
        UUID userId = UUID.randomUUID();

        Membre membre = new Membre();
        membre.setId(userId);

        Notification n1 = new Notification();
        n1.setStatut("ENVOYEE");
        Notification n2 = new Notification();
        n2.setStatut("ENVOYEE");

        when(notificationRepository.findNonLuesByMembreId(userId)).thenReturn(List.of(n1, n2));

        notificationHistoryService.marquerToutesCommeLues(userId);

        assertThat(n1.getStatut()).isEqualTo("LUE");
        assertThat(n2.getStatut()).isEqualTo("LUE");
        verify(notificationRepository, times(2)).persist(any(Notification.class));
    }

    @Test
    @DisplayName("marquerToutesCommeLues ne fait rien si aucune notification non lue")
    void marquerToutesCommeLues_noUnread_doesNothing() {
        UUID userId = UUID.randomUUID();
        when(notificationRepository.findNonLuesByMembreId(userId)).thenReturn(List.of());

        notificationHistoryService.marquerToutesCommeLues(userId);

        verify(notificationRepository, never()).persist(any(Notification.class));
    }

    @Test
    @DisplayName("compterNotificationsNonLues délègue au repository")
    void compterNotificationsNonLues_delegatesToRepository() {
        UUID userId = UUID.randomUUID();
        // count(String query, Object... params) — matcher varargs
        when(notificationRepository.count(anyString(), any(Object[].class))).thenReturn(5L);

        long count = notificationHistoryService.compterNotificationsNonLues(userId);

        assertThat(count).isEqualTo(5L);
    }

    @Test
    @DisplayName("obtenirNotificationsNonLues délègue au repository")
    void obtenirNotificationsNonLues_delegatesToRepository() {
        UUID userId = UUID.randomUUID();
        Notification n = new Notification();
        n.setStatut("ENVOYEE");
        when(notificationRepository.findNonLuesByMembreId(userId)).thenReturn(List.of(n));

        List<Notification> result = notificationHistoryService.obtenirNotificationsNonLues(userId);

        assertThat(result).hasSize(1);
        verify(notificationRepository).findNonLuesByMembreId(userId);
    }

    @Test
    @DisplayName("obtenirStatistiques retourne les bonnes clés et calculs")
    void obtenirStatistiques_returnsCorrectStats() {
        UUID userId = UUID.randomUUID();

        Notification envoyee = new Notification();
        envoyee.setStatut("ENVOYEE");
        envoyee.setTypeNotification("EMAIL");

        Notification lue = new Notification();
        lue.setStatut("LUE");
        lue.setTypeNotification("IN_APP");

        Notification echec = new Notification();
        echec.setStatut("ECHEC_ENVOI");
        echec.setTypeNotification("EMAIL");

        when(notificationRepository.findByMembreId(userId)).thenReturn(List.of(envoyee, lue, echec));

        Map<String, Object> stats = notificationHistoryService.obtenirStatistiques(userId);

        assertThat(stats).containsKeys("total", "nonLues", "succes", "echecs", "parType");
        assertThat(stats.get("total")).isEqualTo(3);
        assertThat(stats.get("nonLues")).isEqualTo(2L); // envoyee + echec
        assertThat(stats.get("succes")).isEqualTo(2L);  // envoyee + lue
        assertThat(stats.get("echecs")).isEqualTo(1L);  // echec
    }

    @Test
    @DisplayName("obtenirStatistiques avec liste vide retourne des zéros")
    void obtenirStatistiques_emptyList_returnsZeros() {
        UUID userId = UUID.randomUUID();
        when(notificationRepository.findByMembreId(userId)).thenReturn(List.of());

        Map<String, Object> stats = notificationHistoryService.obtenirStatistiques(userId);

        assertThat(stats.get("total")).isEqualTo(0);
        assertThat(stats.get("nonLues")).isEqualTo(0L);
        assertThat(stats.get("succes")).isEqualTo(0L);
        assertThat(stats.get("echecs")).isEqualTo(0L);
    }

    @Test
    @DisplayName("obtenirStatistiques avec statut ERREUR_TECHNIQUE couvre la branche || de la lambda L133")
    void obtenirStatistiques_erreurTechnique_coversSecondOrBranch() {
        UUID userId = UUID.randomUUID();

        // ECHEC_ENVOI → première branche (déjà couverte dans le test principal)
        // ERREUR_TECHNIQUE → deuxième branche de || (non encore couverte)
        Notification erreurTechnique = new Notification();
        erreurTechnique.setStatut("ERREUR_TECHNIQUE");
        erreurTechnique.setTypeNotification("SMS");

        // Aussi tester le filtre "nonLues" : ERREUR_TECHNIQUE != "LUE" → comptée comme nonLue
        when(notificationRepository.findByMembreId(userId)).thenReturn(List.of(erreurTechnique));

        Map<String, Object> stats = notificationHistoryService.obtenirStatistiques(userId);

        assertThat(stats.get("total")).isEqualTo(1);
        assertThat(stats.get("nonLues")).isEqualTo(1L);
        assertThat(stats.get("succes")).isEqualTo(0L);
        assertThat(stats.get("echecs")).isEqualTo(1L); // ERREUR_TECHNIQUE → compté comme échec
    }

    @Test
    @DisplayName("obtenirStatistiques avec type null classe la notification sous INCONNU")
    void obtenirStatistiques_nullType_classifiedAsInconnu() {
        UUID userId = UUID.randomUUID();

        Notification sansType = new Notification();
        sansType.setStatut("ENVOYEE");
        sansType.setTypeNotification(null);

        when(notificationRepository.findByMembreId(userId)).thenReturn(List.of(sansType));

        Map<String, Object> stats = notificationHistoryService.obtenirStatistiques(userId);

        @SuppressWarnings("unchecked")
        Map<String, Long> parType = (Map<String, Long>) stats.get("parType");
        assertThat(parType).containsKey("INCONNU");
        assertThat(parType.get("INCONNU")).isEqualTo(1L);
    }
}
