package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
import dev.lions.unionflow.server.api.dto.notification.response.NotificationResponse;
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 dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.TemplateNotificationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests complémentaires pour {@link NotificationService} — cas limites de creerNotification,
 * convertToDTO, convertToEntity et envoyerEmail avec des valeurs null ou vides.
 */
@QuarkusTest
@DisplayName("NotificationService — cas limites")
class NotificationServiceCoverageTest {

    @Inject
    NotificationService notificationService;

    @InjectMock
    KeycloakService keycloakService;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    NotificationRepository notificationRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    @InjectMock
    TemplateNotificationRepository templateNotificationRepository;

    private static final UUID MEMBRE_ID = UUID.fromString("cccccccc-1111-2222-3333-dddddddddddd");

    @BeforeEach
    void setUp() {
        when(keycloakService.getCurrentUserEmail()).thenReturn("system@test.com");
    }

    // =========================================================================
    // L388-392 : envoyerEmail — membre null → ECHEC_ENVOI (nc)
    // =========================================================================

    /**
     * Couvre les lignes 389-392 (nc) : notification.getMembre() == null
     * → LOG.warnf + setStatut("ECHEC_ENVOI") + setMessageErreur + return.
     *
     * <p>Pour atteindre envoyerEmail avec membre=null, on crée une notification de type EMAIL
     * sans membreId. Dans convertToEntity, si membreId==null, le membre n'est pas chargé
     * → notification.getMembre()==null → envoyerEmail prend la branche L388 true → L389-392.
     */
    @Test
    @DisplayName("envoyerEmail — membre null → ECHEC_ENVOI (couvre L389-392)")
    void envoyerEmail_membreNull_couvreL389a392() {
        // Notification EMAIL sans membreId → notification.getMembre()==null après convertToEntity
        // → envoyerEmail: if (getMembre() == null) → branches L388 true → L389-392 couverts
        CreateNotificationRequest request = CreateNotificationRequest.builder()
                .typeNotification("EMAIL")
                // membreId=null → convertToEntity ne charge pas de membre
                .sujet("Test sans membre")
                .corps("Corps")
                .build();

        // Ne doit pas lever d'exception (envoyerEmail absorbe l'échec à L390-392)
        assertThatCode(() -> notificationService.creerNotification(request)).doesNotThrowAnyException();
    }

    /**
     * Couvre la branche L388 : membre != null mais email == null
     * → même bloc de LOG.warn + ECHEC_ENVOI.
     *
     * <p>On injecte un membre sans email pour que getMembre().getEmail() == null soit vrai.
     */
    @Test
    @DisplayName("envoyerEmail — membre sans email → ECHEC_ENVOI (couvre L388 branche email==null)")
    void envoyerEmail_membreSansEmail_couvreL388EmailNull() {
        Membre membreSansEmail = new Membre();
        membreSansEmail.setId(MEMBRE_ID);
        membreSansEmail.setNom("SansEmail");
        membreSansEmail.setPrenom("Test");
        membreSansEmail.setEmail(null); // email=null → L388: getMembre().getEmail() == null → true
        membreSansEmail.setNumeroMembre("SANS-EMAIL-001");
        membreSansEmail.setActif(true);

        when(membreRepository.findByIdOptional(MEMBRE_ID)).thenReturn(Optional.of(membreSansEmail));

        CreateNotificationRequest request = CreateNotificationRequest.builder()
                .typeNotification("EMAIL")
                .membreId(MEMBRE_ID)
                .sujet("Test email null")
                .corps("Corps")
                .build();

        // Ne doit pas lever d'exception
        assertThatCode(() -> notificationService.creerNotification(request)).doesNotThrowAnyException();
    }

    // =========================================================================
    // L209 : canaux.isEmpty() → true → canaux = List.of("IN_APP")
    // =========================================================================

    /**
     * Couvre la branche L209 : canaux != null MAIS canaux.isEmpty() == true
     * → canaux = List.of("IN_APP").
     *
     * <p>Les tests existants couvrent `canaux == null` (branche null → short-circuit) via
     * testEnvoyerNotificationsGroupees_canauxNull_utiliseInAppParDefaut.
     * Ici on couvre explicitement `canaux.isEmpty()` (seconde partie de la condition ||).
     */
    @Test
    @DisplayName("envoyerNotificationsGroupees — canaux vide (empty) → utilise IN_APP (couvre L209 isEmpty branch)")
    void envoyerNotificationsGroupees_canauxEmpty_utiliseInApp() {
        Membre membre = new Membre();
        membre.setId(MEMBRE_ID);
        membre.setNom("Coverage");
        membre.setPrenom("Test");
        membre.setEmail("coverage@test.com");
        membre.setNumeroMembre("COVERAGE-001");
        membre.setActif(true);

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

        // canaux = List.of() → vide → L209: canaux.isEmpty() == true → canaux = List.of("IN_APP")
        int result = notificationService.envoyerNotificationsGroupees(
                List.of(MEMBRE_ID), "Sujet coverage", "Corps", List.of());

        assertThat(result).isEqualTo(1);
    }

    // =========================================================================
    // L317 : notification.getMembre() == null → membreId non renseigné dans convertToDTO
    // =========================================================================

    /**
     * Couvre la branche L317 false : notification.getMembre() == null → dto.membreId reste null.
     *
     * <p>Atteint via trouverNotificationParId avec une notification sans membre
     * (getMembre()==null).
     */
    @Test
    @DisplayName("convertToDTO(Notification) — membre null → membreId non renseigné (couvre L317 branche false)")
    void convertToDTO_notification_membreNull_membreIdNonRenseigne() {
        UUID notifId = UUID.randomUUID();

        Notification notifSansMembre = new Notification();
        notifSansMembre.setId(notifId);
        notifSansMembre.setTypeNotification("IN_APP");
        notifSansMembre.setPriorite("NORMALE");
        notifSansMembre.setStatut("EN_ATTENTE");
        notifSansMembre.setSujet("Test sans membre");
        notifSansMembre.setCorps("Corps");
        // membre=null → L317: if (getMembre() != null) = false → membreId pas setté

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

        var dto = notificationService.trouverNotificationParId(notifId);
        assertThat(dto).isNotNull();
        assertThat(dto.getMembreId()).isNull();
    }

    // =========================================================================
    // creerNotification:86 — happy path IN_APP sans membre (8I, 0B)
    // =========================================================================

    /**
     * Couvre les instructions manquantes à la ligne 86 (début de creerNotification).
     *
     * <p>Utilise le mock du repository pour éviter la transaction réelle.
     * Le type IN_APP évite l'appel au Mailer.
     */
    @Test
    @DisplayName("creerNotification IN_APP sans membreId — couvre les instructions ligne 86 (8I, 0B)")
    void creerNotification_inApp_sansMembre_couvreL86() {
        doNothing().when(notificationRepository).persist(any(Notification.class));

        CreateNotificationRequest request = CreateNotificationRequest.builder()
                .typeNotification("IN_APP")
                .sujet("Test couverture ligne 86")
                .corps("Corps de test")
                .build();

        NotificationResponse response = notificationService.creerNotification(request);
        assertThat(response).isNotNull();
        assertThat(response.getTypeNotification()).isEqualTo("IN_APP");
    }

    // =========================================================================
    // envoyerNotificationsGroupees:192 — happy path (5I, 0B)
    // =========================================================================

    /**
     * Couvre les instructions manquantes à la ligne 192 (début de envoyerNotificationsGroupees).
     *
     * <p>Passe la liste avec un membre valide mocké et des canaux IN_APP.
     */
    @Test
    @DisplayName("envoyerNotificationsGroupees — couvre les instructions ligne 192 (5I, 0B)")
    void envoyerNotificationsGroupees_avecMembreValide_couvreL192() {
        Membre membre = new Membre();
        membre.setId(MEMBRE_ID);
        membre.setNom("Coverage192");
        membre.setPrenom("Test");
        membre.setEmail("l192@test.com");
        membre.setNumeroMembre("L192-001");
        membre.setActif(true);

        when(membreRepository.findByIdOptional(MEMBRE_ID)).thenReturn(Optional.of(membre));
        doNothing().when(notificationRepository).persist(any(Notification.class));

        int result = notificationService.envoyerNotificationsGroupees(
                List.of(MEMBRE_ID), "Sujet L192", "Corps L192", List.of("IN_APP"));

        assertThat(result).isEqualTo(1);
    }

    // =========================================================================
    // L355 : dto.membreId() == null → bloc lookup membre sauté dans convertToEntity
    // =========================================================================

    /**
     * Couvre la branche L355 false : dto.membreId() == null → le bloc de lookup membre est sauté.
     *
     * <p>Tous les tests existants qui fonctionnent utilisent soit membreId non-null, soit
     * levèrent une exception avant d'atteindre cette branche. Ce test crée une notification
     * IN_APP sans membreId pour couvrir explicitement la branche false de L355.
     */
    @Test
    @DisplayName("convertToEntity(CreateNotificationRequest) — membreId null → membre non chargé (couvre L355 branche false)")
    void convertToEntity_membreIdNull_membreNonCharge() {
        // membreId=null → L355: if (dto.membreId() != null) = false → membre non chargé
        // type IN_APP → pas d'envoi email → pas d'interaction avec le mailer
        CreateNotificationRequest request = CreateNotificationRequest.builder()
                .typeNotification("IN_APP")
                // membreId=null intentionnel pour couvrir L355 false
                .sujet("Test membreId null")
                .corps("Corps")
                .build();

        // Ne doit pas lever d'exception
        assertThatCode(() -> notificationService.creerNotification(request)).doesNotThrowAnyException();
    }
}
