package dev.lions.unionflow.server.service;

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.doThrow;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.notification.request.CreateNotificationRequest;
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.arc.ClientProxy;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests pour les blocs catch de {@link NotificationService} — creerNotification et envoyerNotificationsGroupees.
 *
 * <p>Technique : réflexion pour remplacer le {@code Mailer} par un mock qui réussit l'envoi,
 * tandis que {@code NotificationRepository} est configuré via {@code @InjectMock} pour lancer
 * une exception lors de l'appel à {@code persist}.
 */
@QuarkusTest
@DisplayName("NotificationService — catch blocks dans creerNotification et envoyerNotificationsGroupees")
class NotificationServiceCatchCoverageTest {

    @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("ee111111-aaaa-bbbb-cccc-ffffffffffff");

    /** Le vrai Mailer (remplacé par un silencieux pour éviter SMTP). */
    private Mailer originalMailer;

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

        // Remplacer le Mailer par un mock silencieux (ne throw pas) pour pouvoir atteindre L412
        // après le bloc try/catch interne d'envoyerEmail.
        NotificationService realService = unwrapProxy(notificationService);
        Field mailerField = findField(realService.getClass(), "mailer");
        mailerField.setAccessible(true);
        originalMailer = (Mailer) mailerField.get(realService);

        Mailer silentMailer = org.mockito.Mockito.mock(Mailer.class);
        doNothing().when(silentMailer).send(any(Mail.class));
        mailerField.set(realService, silentMailer);
    }

    @AfterEach
    void restoreMailer() throws Exception {
        if (originalMailer != null) {
            NotificationService realService = unwrapProxy(notificationService);
            Field mailerField = findField(realService.getClass(), "mailer");
            mailerField.setAccessible(true);
            mailerField.set(realService, originalMailer);
        }
    }

    // =========================================================================
    // L98-100 : catch(Exception e) dans creerNotification()
    // =========================================================================

    /**
     * Couvre les lignes 98-100 de {@code creerNotification()}.
     *
     * <p>Séquence d'exécution :
     * <ol>
     *   <li>{@code creerNotification()} appelle {@code notificationRepository.persist(notification)}
     *       à L91 → {@code doNothing()} (premier appel).</li>
     *   <li>Le type est EMAIL → {@code envoyerEmail(notification)} est appelée.</li>
     *   <li>Dans {@code envoyerEmail()} : le membre a un email → le try interne
     *       ({@code mailer.send()}) réussit (mock silencieux).</li>
     *   <li>À L412, {@code notificationRepository.persist(notification)} est appelée à
     *       nouveau depuis {@code envoyerEmail()} — HORS du try/catch interne d'envoyerEmail —
     *       et lance une {@code RuntimeException}.</li>
     *   <li>Cette exception remonte depuis {@code envoyerEmail()} vers le catch à L98 de
     *       {@code creerNotification()} → couvre L98, L99, L100.</li>
     *   <li>{@code creerNotification()} ne relance pas l'exception → retourne le DTO normalement.</li>
     * </ol>
     */
    @Test
    @DisplayName("creerNotification EMAIL : persist à L412 dans envoyerEmail throw → catch L98-100 couvert")
    void creerNotification_emailType_persistAtL412Throws_coversCatchL98to100() {
        Membre membre = buildMembre();
        when(membreRepository.findByIdOptional(MEMBRE_ID)).thenReturn(Optional.of(membre));

        // Premier appel persist (L91 dans creerNotification) : OK
        // Deuxième appel persist (L412 dans envoyerEmail, hors try/catch interne) : throw
        // On utilise doNothing().doThrow() pour différencier les deux appels successifs
        doNothing()
                .doThrow(new RuntimeException("Simulated persist failure at L412 — outside envoyerEmail inner catch"))
                .when(notificationRepository).persist(any(Notification.class));

        CreateNotificationRequest request = CreateNotificationRequest.builder()
                .typeNotification("EMAIL")
                .membreId(MEMBRE_ID)
                .sujet("Test catch L98-100")
                .corps("Corps du test L98")
                .build();

        // L98-100 est couvert : exception capturée, pas relancée → pas de throw ici
        assertThatCode(() -> notificationService.creerNotification(request))
                .doesNotThrowAnyException();
    }

    // =========================================================================
    // L234-235 : catch(IllegalArgumentException e) dans envoyerNotificationsGroupees()
    // =========================================================================

    /**
     * Couvre les lignes 234-235 de {@code envoyerNotificationsGroupees()}.
     *
     * <p>La boucle interne (L213-236) contient un try/catch(IllegalArgumentException) autour de
     * la création et persistance de la notification par canal. Si {@code notificationRepository.persist()}
     * lance une {@code IllegalArgumentException}, le catch à L234 est déclenché, loggant un warning
     * et continuant la boucle.
     *
     * <p>Ici, on configure le mock pour que le premier persist (creerNotification) réussisse,
     * et que le persist dans la boucle des canaux lance une {@code IllegalArgumentException}.
     */
    @Test
    @DisplayName("envoyerNotificationsGroupees : persist dans boucle canal throw IAE → catch L234-235 couvert")
    void envoyerNotificationsGroupees_persistThrowsIAE_coversCatchL234to235() {
        Membre membre = buildMembre();
        when(membreRepository.findByIdOptional(MEMBRE_ID)).thenReturn(Optional.of(membre));

        // Le persist dans la boucle canal (L227) lance IllegalArgumentException → catch L234-235
        doThrow(new IllegalArgumentException("Simulated IAE in canal loop — covers L234-235"))
                .when(notificationRepository).persist(any(Notification.class));

        // La méthode doit retourner 0 (notification non comptée car IAE catchée avant l'incrément)
        int result = notificationService.envoyerNotificationsGroupees(
                List.of(MEMBRE_ID), "Sujet IAE Test", "Corps IAE", List.of("IN_APP"));

        // L'exception est catchée → notificationsCreees n'est pas incrémenté → résultat 0
        org.assertj.core.api.Assertions.assertThat(result).isEqualTo(0);
    }

    /**
     * Couvre L234-235 avec plusieurs canaux : le premier canal throw, le second réussit.
     * Vérifie que la boucle continue après le catch (comportement attendu).
     */
    @Test
    @DisplayName("envoyerNotificationsGroupees : IAE sur premier canal, second réussit → résultat 1 (catch L234-235 + continuation)")
    void envoyerNotificationsGroupees_firstCanalThrowsIAE_secondSucceeds_result1() {
        Membre membre = buildMembre();
        when(membreRepository.findByIdOptional(MEMBRE_ID)).thenReturn(Optional.of(membre));

        // Premier appel persist (canal IN_APP) : throw IAE → catch L234-235
        // Deuxième appel persist (canal SMS) : OK → notificationsCreees = 1
        doThrow(new IllegalArgumentException("IAE on first canal"))
                .doNothing()
                .when(notificationRepository).persist(any(Notification.class));

        int result = notificationService.envoyerNotificationsGroupees(
                List.of(MEMBRE_ID), "Sujet multi canal IAE", "Corps", List.of("IN_APP", "SMS"));

        // Premier canal throw (IAE catchée, count=0), second canal réussit (count=1)
        org.assertj.core.api.Assertions.assertThat(result).isEqualTo(1);
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private Membre buildMembre() {
        Membre membre = new Membre();
        membre.setId(MEMBRE_ID);
        membre.setNom("CatchTest");
        membre.setPrenom("Coverage");
        membre.setEmail("catch-coverage@test.com");
        membre.setNumeroMembre("CATCH-COV-001");
        membre.setActif(true);
        return membre;
    }

    @SuppressWarnings("unchecked")
    private NotificationService unwrapProxy(NotificationService proxy) {
        if (proxy instanceof ClientProxy) {
            return (NotificationService) ((ClientProxy) proxy).arc_contextualInstance();
        }
        return proxy;
    }

    private Field findField(Class<?> clazz, String name) throws NoSuchFieldException {
        while (clazz != null) {
            try {
                return clazz.getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException(name);
    }
}
