package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.repository.ConfigurationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

/**
 * Tests complémentaires pour {@link DefaultsService#getString} — bloc catch quand le repository
 * lève une exception (retour du fallback).
 *
 * <p>Stratégie : {@link ConfigurationRepository} est mocké via {@code @InjectMock} pour que
 * {@code findByCle(cle)} lève une {@link RuntimeException}, forçant l'exécution du bloc catch.
 */
@QuarkusTest
@DisplayName("DefaultsService — catch(Exception) dans getString → retour du fallback")
class DefaultsServiceBranchesMissingTest {

    @Inject
    DefaultsService defaultsService;

    @InjectMock
    ConfigurationRepository configRepo;

    // =========================================================================
    // L141-144 : catch(Exception e) dans getString
    //
    // Code source (L132-147) :
    //   public String getString(String cle, String fallback) {
    //     try {
    //       Optional<Configuration> config = configRepo.findByCle(cle);  ← peut lancer exception
    //       if (config.isPresent() && config.get().getValeur() != null
    //               && !config.get().getValeur().isBlank()) {
    //           return config.get().getValeur();
    //       }
    //     } catch (Exception e) {                                          ← L141
    //       LOG.debugf("Configuration '%s' indisponible: %s",             ← L142
    //               cle, e.getMessage());                                  ← L143 (suite)
    //     }                                                                ← L143 (fermeture)
    //     return fallback;                                                 ← L144
    //   }
    //
    // Le test existant DefaultsServiceTest.java utilise le vrai configRepo → Optional.empty()
    // sans exception → les lignes 141-142-143 ne sont jamais atteintes.
    // =========================================================================

    @Nested
    @DisplayName("L141-144 — catch(Exception e) dans getString via mock configRepo")
    class GetStringCatchExceptionTest {

        /**
         * Couvre les lignes 141-144 de DefaultsService :
         * {@code catch(Exception e) { LOG.debugf(...); } return fallback; }
         *
         * <p>On mock {@code configRepo.findByCle} pour lancer une {@link RuntimeException}.
         * Le catch L141 intercepte l'exception, logue le message (L142), et retourne le
         * fallback (L144) sans propager l'exception.
         */
        @Test
        @DisplayName("configRepo.findByCle lève RuntimeException → catch L141 atteint → fallback retourné (L144)")
        void getString_findByCleException_catchL141Atteint_fallbackRetourne() {
            // Mock : findByCle lève une RuntimeException pour simuler une BD inaccessible
            when(configRepo.findByCle(anyString()))
                    .thenThrow(new RuntimeException("Connexion BD perdue"));

            // getString doit attraper l'exception en L141 et retourner le fallback
            String result = defaultsService.getString("une.cle.quelconque", "valeur-fallback");

            // L144 : return fallback → "valeur-fallback"
            assertThat(result).isEqualTo("valeur-fallback");
        }

        /**
         * Variante : l'exception est une {@link IllegalStateException} (sous-classe de
         * {@link RuntimeException} et donc de {@link Exception}).
         *
         * <p>Vérifie que le catch L141 attrape tout type d'Exception, pas seulement RuntimeException.
         * Couvre à nouveau L141-144 avec un type d'exception différent.
         */
        @Test
        @DisplayName("configRepo.findByCle lève IllegalStateException → catch L141 atteint → fallback (L144)")
        void getString_findByCleIllegalState_catchL141Atteint_fallbackRetourne() {
            when(configRepo.findByCle(anyString()))
                    .thenThrow(new IllegalStateException("Entité manager fermé"));

            String result = defaultsService.getString("cle.illegal.state", "fallback-illegal");

            // L144 : return fallback
            assertThat(result).isEqualTo("fallback-illegal");
        }

        /**
         * Vérifie que toutes les méthodes publiques de DefaultsService qui appellent getString()
         * bénéficient aussi du fallback quand configRepo lève une exception.
         *
         * <p>getDevise() → getString(CLE_DEVISE, "XOF") → catch L141 → return "XOF"
         */
        @Test
        @DisplayName("getDevise() via getString() avec exception → fallback 'XOF' retourné (couvre L141-144)")
        void getDevise_configRepoException_retourneFallbackXof() {
            when(configRepo.findByCle(DefaultsService.CLE_DEVISE))
                    .thenThrow(new RuntimeException("Timeout"));

            String devise = defaultsService.getDevise();

            // Le fallback compile-time "XOF" est retourné via la ligne 144
            assertThat(devise).isEqualTo("XOF");
        }

        /**
         * Vérifie que getStatutOrganisation() utilise le fallback quand configRepo lève une exception.
         */
        @Test
        @DisplayName("getStatutOrganisation() avec exception configRepo → fallback 'ACTIVE' (L141-144)")
        void getStatutOrganisation_exception_retourneFallback() {
            when(configRepo.findByCle(DefaultsService.CLE_STATUT_ORG))
                    .thenThrow(new RuntimeException("DB unavailable"));

            String statut = defaultsService.getStatutOrganisation();

            assertThat(statut).isEqualTo("ACTIVE");
        }

        /**
         * Vérifie que getUtilisateurSysteme() utilise le fallback quand configRepo lève une exception.
         */
        @Test
        @DisplayName("getUtilisateurSysteme() avec exception configRepo → fallback 'system' (L141-144)")
        void getUtilisateurSysteme_exception_retourneFallback() {
            when(configRepo.findByCle(DefaultsService.CLE_USER_SYSTEME))
                    .thenThrow(new RuntimeException("Hibernate error"));

            String user = defaultsService.getUtilisateurSysteme();

            assertThat(user).isEqualTo("system");
        }

        /**
         * Vérifie que getMontantCotisation() utilise le fallback BigDecimal.ZERO quand configRepo
         * lève une exception.
         *
         * <p>getMontantCotisation() → getDecimal(CLE_MONTANT_COTISATION, ZERO)
         *   → getString(CLE_MONTANT_COTISATION, null) → catch L141 → return null
         *   → getDecimal voit null → return fallback (BigDecimal.ZERO)
         */
        @Test
        @DisplayName("getMontantCotisation() avec exception configRepo → fallback ZERO retourné (L141-144)")
        void getMontantCotisation_exception_retourneFallbackZero() {
            when(configRepo.findByCle(DefaultsService.CLE_MONTANT_COTISATION))
                    .thenThrow(new RuntimeException("Pool épuisé"));

            BigDecimal montant = defaultsService.getMontantCotisation();

            // getString retourne null (fallback passé à getDecimal est null)
            // getDecimal voit val == null → retourne son propre fallback = BigDecimal.ZERO
            assertThat(montant).isEqualByComparingTo(BigDecimal.ZERO);
        }

        /**
         * Vérifie que le message de l'exception est bien loggué (L142) en testant avec une
         * exception dont le message est non-null.
         *
         * <p>On ne peut pas vérifier directement le contenu du log, mais on vérifie que
         * la méthode s'exécute sans NPE malgré l'exception (e.getMessage() non null requis).
         */
        @Test
        @DisplayName("Exception avec message non-null → LOG.debugf appelé sans NPE (L142)")
        void getString_exceptionAvecMessage_l142Appelee_sanNPE() {
            when(configRepo.findByCle(anyString()))
                    .thenThrow(new RuntimeException("Message d'erreur explicite"));

            // Si LOG.debugf en L142 lançait une NPE (e.getMessage() == null), le test échouerait
            // → ici e.getMessage() = "Message d'erreur explicite" (non-null) → OK
            String result = defaultsService.getString("cle.message.test", "fallback-l142");

            assertThat(result).isEqualTo("fallback-l142");
        }

        /**
         * Vérifie le comportement quand l'exception a un message null (e.getMessage() == null).
         *
         * <p>LOG.debugf en L142 reçoit {@code e.getMessage()} = null → LOG.debugf gère null
         * sans NPE (JBoss Logging est null-safe pour les arguments de format).
         */
        @Test
        @DisplayName("Exception avec message null → LOG.debugf avec null sans NPE (L142 robustesse)")
        void getString_exceptionSansMessage_l142AvecNull_sanNPE() {
            // NullPointerException n'a pas de message par défaut
            when(configRepo.findByCle(anyString()))
                    .thenThrow(new NullPointerException()); // getMessage() = null

            // Doit s'exécuter sans NPE dans le log (JBoss Logger.debugf est null-safe)
            String result = defaultsService.getString("cle.null.message", "fallback-null-msg");

            assertThat(result).isEqualTo("fallback-null-msg");
        }
    }
}
