package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Notification;
import dev.lions.unionflow.server.entity.Organisation;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.TestTransaction;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

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

@QuarkusTest
class NotificationRepositoryTest {

    @Inject
    NotificationRepository notificationRepository;

    @Inject
    MembreRepository membreRepository;

    @Inject
    OrganisationRepository organisationRepository;

    // ── helpers ───────────────────────────────────────────────────────────

    private Membre persistMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("MEM-N-" + UUID.randomUUID().toString().substring(0, 8));
        m.setPrenom("Prénom");
        m.setNom("NotifTest");
        m.setEmail("mb-notif-" + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setActif(true);
        membreRepository.persist(m);
        return m;
    }

    private Organisation persistOrganisation() {
        Organisation o = new Organisation();
        o.setNom("Org Notif Test");
        o.setTypeOrganisation("ASSOCIATION");
        o.setStatut("ACTIVE");
        o.setEmail("org-notif-" + UUID.randomUUID() + "@test.com");
        o.setActif(true);
        organisationRepository.persist(o);
        return o;
    }

    private Notification persistNotification(Membre membre,
                                             Organisation organisation,
                                             String type,
                                             String statut,
                                             String priorite,
                                             LocalDateTime dateEnvoiPrevue) {
        Notification n = new Notification();
        n.setTypeNotification(type);
        n.setStatut(statut);
        n.setPriorite(priorite);
        n.setMembre(membre);
        n.setOrganisation(organisation);
        n.setDateEnvoiPrevue(dateEnvoiPrevue);
        n.setNombreTentatives(0);
        n.setActif(true);
        notificationRepository.persist(n);
        return n;
    }

    // ── tests hérités (inchangés) ─────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findById retourne null pour UUID inexistant")
    void findById_inexistant_returnsNull() {
        assertThat(notificationRepository.findById(UUID.randomUUID())).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findNotificationById retourne empty pour UUID inexistant")
    void findNotificationById_inexistant_returnsEmpty() {
        Optional<Notification> opt = notificationRepository.findNotificationById(UUID.randomUUID());
        assertThat(opt).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("listAll retourne une liste")
    void listAll_returnsList() {
        List<Notification> list = notificationRepository.listAll();
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("count retourne un nombre >= 0")
    void count_returnsNonNegative() {
        assertThat(notificationRepository.count()).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @TestTransaction
    @DisplayName("findByMembreId retourne une liste")
    void findByMembreId_returnsList() {
        List<Notification> list = notificationRepository.findByMembreId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findNonLuesByMembreId retourne une liste")
    void findNonLuesByMembreId_returnsList() {
        List<Notification> list = notificationRepository.findNonLuesByMembreId(UUID.randomUUID());
        assertThat(list).isNotNull();
    }

    // ── findNotificationById ──────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findNotificationById retrouve la notification persistée")
    void findNotificationById_found_returnsPresent() {
        Membre m = persistMembre();
        Notification n = persistNotification(m, null, "ALERTE", "EN_ATTENTE", "NORMALE",
                LocalDateTime.now().plusHours(1));

        Optional<Notification> result = notificationRepository.findNotificationById(n.getId());

        assertThat(result).isPresent();
        assertThat(result.get().getId()).isEqualTo(n.getId());
    }

    // ── findByMembreId ────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByMembreId retourne liste vide pour membre sans notifications")
    void findByMembreId_noNotifications_returnsEmpty() {
        Membre m = persistMembre();

        List<Notification> result = notificationRepository.findByMembreId(m.getId());

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

    @Test
    @TestTransaction
    @DisplayName("findByMembreId retrouve les notifications du membre")
    void findByMembreId_withNotifications_returnsList() {
        Membre m = persistMembre();
        persistNotification(m, null, "INFO", "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusMinutes(5));
        persistNotification(m, null, "ALERTE", "ENVOYEE", "HAUTE", LocalDateTime.now().minusMinutes(1));

        List<Notification> result = notificationRepository.findByMembreId(m.getId());

        assertThat(result).hasSize(2);
    }

    // ── findNonLuesByMembreId ─────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findNonLuesByMembreId retourne liste vide pour membre sans notification NON_LUE")
    void findNonLuesByMembreId_noNonLues_returnsEmpty() {
        Membre m = persistMembre();
        persistNotification(m, null, "INFO", "LUE", "NORMALE", LocalDateTime.now().plusMinutes(5));

        List<Notification> result = notificationRepository.findNonLuesByMembreId(m.getId());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findNonLuesByMembreId retrouve uniquement les notifications NON_LUE du membre")
    void findNonLuesByMembreId_withNonLues_returnsList() {
        Membre m = persistMembre();
        persistNotification(m, null, "ALERTE", "NON_LUE", "HAUTE", LocalDateTime.now().plusMinutes(10));
        persistNotification(m, null, "INFO", "LUE", "NORMALE", LocalDateTime.now().minusMinutes(5));

        List<Notification> result = notificationRepository.findNonLuesByMembreId(m.getId());

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getStatut()).isEqualTo("NON_LUE");
    }

    // ── findByOrganisationId ──────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId retourne liste vide pour organisation inexistante")
    void findByOrganisationId_inexistant_returnsEmpty() {
        List<Notification> result = notificationRepository.findByOrganisationId(UUID.randomUUID());
        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByOrganisationId retrouve les notifications de l'organisation")
    void findByOrganisationId_withNotifications_returnsList() {
        Organisation o = persistOrganisation();
        persistNotification(null, o, "RAPPEL", "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusHours(2));
        persistNotification(null, o, "INFO", "ENVOYEE", "BASSE", LocalDateTime.now().minusHours(1));

        List<Notification> result = notificationRepository.findByOrganisationId(o.getId());

        assertThat(result).hasSize(2);
    }

    // ── findByType ────────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByType retourne liste vide pour type inexistant")
    void findByType_inexistant_returnsEmpty() {
        List<Notification> result = notificationRepository.findByType("TYPE_INEXISTANT_" + UUID.randomUUID());
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByType retrouve les notifications du type demandé")
    void findByType_withMatch_returnsList() {
        Membre m = persistMembre();
        String uniqueType = "TYPE_" + UUID.randomUUID().toString().substring(0, 8);
        persistNotification(m, null, uniqueType, "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusMinutes(30));

        List<Notification> result = notificationRepository.findByType(uniqueType);

        assertThat(result).isNotEmpty();
        assertThat(result).allMatch(n -> n.getTypeNotification().equals(uniqueType));
    }

    // ── findByStatut ──────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByStatut retourne liste vide pour statut inexistant")
    void findByStatut_inexistant_returnsEmpty() {
        List<Notification> result = notificationRepository.findByStatut("STATUT_INEXISTANT_" + UUID.randomUUID());
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByStatut retrouve les notifications du statut demandé")
    void findByStatut_withMatch_returnsList() {
        Membre m = persistMembre();
        persistNotification(m, null, "ALERTE", "PROGRAMMEE", "HAUTE", LocalDateTime.now().plusDays(1));

        List<Notification> result = notificationRepository.findByStatut("PROGRAMMEE");

        assertThat(result).isNotEmpty();
        assertThat(result).allMatch(n -> "PROGRAMMEE".equals(n.getStatut()));
    }

    // ── findByPriorite ────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByPriorite retourne liste vide pour priorité inexistante")
    void findByPriorite_inexistant_returnsEmpty() {
        List<Notification> result = notificationRepository.findByPriorite("PRIORITE_INCONNUE_" + UUID.randomUUID());
        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByPriorite retrouve les notifications de la priorité demandée")
    void findByPriorite_withMatch_returnsList() {
        Membre m = persistMembre();
        persistNotification(m, null, "INFO", "EN_ATTENTE", "CRITIQUE", LocalDateTime.now().plusHours(1));

        List<Notification> result = notificationRepository.findByPriorite("CRITIQUE");

        assertThat(result).isNotEmpty();
        assertThat(result).allMatch(n -> "CRITIQUE".equals(n.getPriorite()));
    }

    // ── findEnAttenteEnvoi ────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findEnAttenteEnvoi retourne liste (éventuellement vide)")
    void findEnAttenteEnvoi_returnsNonNullList() {
        List<Notification> result = notificationRepository.findEnAttenteEnvoi();
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEnAttenteEnvoi inclut les notifications EN_ATTENTE dont la date est passée")
    void findEnAttenteEnvoi_withEnAttentePassee_includesNotification() {
        Membre m = persistMembre();
        Notification n = persistNotification(m, null, "RAPPEL", "EN_ATTENTE", "NORMALE",
                LocalDateTime.now().minusMinutes(5));

        List<Notification> result = notificationRepository.findEnAttenteEnvoi();

        assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEnAttenteEnvoi inclut les notifications PROGRAMMEE dont la date est passée")
    void findEnAttenteEnvoi_withProgrammeePassee_includesNotification() {
        Membre m = persistMembre();
        Notification n = persistNotification(m, null, "INFO", "PROGRAMMEE", "BASSE",
                LocalDateTime.now().minusHours(1));

        List<Notification> result = notificationRepository.findEnAttenteEnvoi();

        assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEnAttenteEnvoi exclut les notifications dont la date d'envoi est dans le futur")
    void findEnAttenteEnvoi_futurDate_excluded() {
        Membre m = persistMembre();
        Notification n = persistNotification(m, null, "RAPPEL", "EN_ATTENTE", "NORMALE",
                LocalDateTime.now().plusHours(24));

        List<Notification> result = notificationRepository.findEnAttenteEnvoi();

        assertThat(result).noneMatch(notif -> notif.getId().equals(n.getId()));
    }

    // ── findEchoueesRetentables ───────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables retourne liste (éventuellement vide)")
    void findEchoueesRetentables_returnsNonNullList() {
        List<Notification> result = notificationRepository.findEchoueesRetentables();
        assertThat(result).isNotNull();
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables inclut les notifications ECHEC_ENVOI avec moins de 5 tentatives")
    void findEchoueesRetentables_withEchecEnvoi_includesNotification() {
        Membre m = persistMembre();
        Notification n = new Notification();
        n.setTypeNotification("ALERTE");
        n.setStatut("ECHEC_ENVOI");
        n.setPriorite("HAUTE");
        n.setNombreTentatives(2);
        n.setDateEnvoiPrevue(LocalDateTime.now().minusHours(1));
        n.setMembre(m);
        n.setActif(true);
        notificationRepository.persist(n);

        List<Notification> result = notificationRepository.findEchoueesRetentables();

        assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables inclut les notifications ERREUR_TECHNIQUE avec moins de 5 tentatives")
    void findEchoueesRetentables_withErreurTechnique_includesNotification() {
        Membre m = persistMembre();
        Notification n = new Notification();
        n.setTypeNotification("INFO");
        n.setStatut("ERREUR_TECHNIQUE");
        n.setPriorite("NORMALE");
        n.setNombreTentatives(1);
        n.setDateEnvoiPrevue(LocalDateTime.now().minusMinutes(30));
        n.setMembre(m);
        n.setActif(true);
        notificationRepository.persist(n);

        List<Notification> result = notificationRepository.findEchoueesRetentables();

        assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
    }

    @Test
    @TestTransaction
    @DisplayName("findEchoueesRetentables exclut les notifications avec 5 tentatives ou plus")
    void findEchoueesRetentables_maxTentativesReached_excluded() {
        Membre m = persistMembre();
        Notification n = new Notification();
        n.setTypeNotification("RAPPEL");
        n.setStatut("ECHEC_ENVOI");
        n.setPriorite("NORMALE");
        n.setNombreTentatives(5);
        n.setDateEnvoiPrevue(LocalDateTime.now().minusHours(2));
        n.setMembre(m);
        n.setActif(true);
        notificationRepository.persist(n);

        List<Notification> result = notificationRepository.findEchoueesRetentables();

        assertThat(result).noneMatch(notif -> notif.getId().equals(n.getId()));
    }
}
