package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.Configuration;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

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

@QuarkusTest
class DefaultsServiceTest {

    @Inject
    DefaultsService defaultsService;

    @Inject
    EntityManager em;

    @Test
    @DisplayName("getDevise retourne une devise par défaut")
    void getDevise_returnsDefault() {
        String devise = defaultsService.getDevise();
        assertThat(devise).isNotNull();
        assertThat(devise).isEqualTo("XOF");
    }

    @Test
    @DisplayName("getStatutOrganisation retourne un statut par défaut")
    void getStatutOrganisation_returnsDefault() {
        String statut = defaultsService.getStatutOrganisation();
        assertThat(statut).isNotNull();
        assertThat(statut).isEqualTo("ACTIVE");
    }

    @Test
    @DisplayName("getTypeOrganisation retourne un type par défaut")
    void getTypeOrganisation_returnsDefault() {
        String type = defaultsService.getTypeOrganisation();
        assertThat(type).isNotNull();
        assertThat(type).isEqualTo("ASSOCIATION");
    }

    @Test
    @DisplayName("getUtilisateurSysteme retourne un identifiant système")
    void getUtilisateurSysteme_returnsDefault() {
        String user = defaultsService.getUtilisateurSysteme();
        assertThat(user).isNotNull();
        assertThat(user).isEqualTo("system");
    }

    @Test
    @DisplayName("getMontantCotisation retourne un montant non null")
    void getMontantCotisation_returnsNonNull() {
        BigDecimal montant = defaultsService.getMontantCotisation();
        assertThat(montant).isNotNull();
    }

    @Test
    @DisplayName("getString avec clé inexistante retourne le fallback")
    void getString_cleInexistante_returnsFallback() {
        String value = defaultsService.getString("cle.inexistante." + System.currentTimeMillis(), "FALLBACK");
        assertThat(value).isEqualTo("FALLBACK");
    }

    @Test
    @DisplayName("getDecimal avec clé inexistante retourne le fallback BigDecimal")
    void getDecimal_cleInexistante_returnsFallback() {
        BigDecimal value = defaultsService.getDecimal("cle.inexistante.decimal." + System.currentTimeMillis(),
                new BigDecimal("42.0"));
        assertThat(value).isEqualByComparingTo(new BigDecimal("42.0"));
    }

    @Test
    @DisplayName("getBoolean avec clé inexistante retourne le fallback")
    void getBoolean_cleInexistante_returnsFallback() {
        boolean value = defaultsService.getBoolean("cle.inexistante.bool." + System.currentTimeMillis(), true);
        assertThat(value).isTrue();
    }

    @Test
    @DisplayName("getBoolean avec clé inexistante et fallback false retourne false")
    void getBoolean_cleInexistante_fallbackFalse_returnsFalse() {
        boolean value = defaultsService.getBoolean("cle.inexistante.bool2." + System.currentTimeMillis(), false);
        assertThat(value).isFalse();
    }

    @Test
    @DisplayName("getInt avec clé inexistante retourne le fallback entier")
    void getInt_cleInexistante_returnsFallback() {
        int value = defaultsService.getInt("cle.inexistante.int." + System.currentTimeMillis(), 99);
        assertThat(value).isEqualTo(99);
    }

    @Test
    @DisplayName("getInt avec clé inexistante et fallback 0 retourne 0")
    void getInt_cleInexistante_fallbackZero_returnsZero() {
        int value = defaultsService.getInt("cle.inexistante.int2." + System.currentTimeMillis(), 0);
        assertThat(value).isEqualTo(0);
    }

    // ─── Tests avec données réelles en DB (couvrent les branches non testées) ──

    @Test
    @TestTransaction
    @DisplayName("getString avec clé existante et valeur non vide retourne la valeur DB")
    void getString_cleExistanteAvecValeur_retourneValeurDB() {
        String cle = "test.string.valeur." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("valeur-db").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

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

        assertThat(result).isEqualTo("valeur-db");
    }

    @Test
    @TestTransaction
    @DisplayName("getString avec clé existante et valeur vide retourne le fallback")
    void getString_cleExistanteAvecValeurVide_retourneFallback() {
        String cle = "test.string.vide." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("  ").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

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

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

    @Test
    @TestTransaction
    @DisplayName("getString avec clé existante et valeur null retourne le fallback")
    void getString_cleExistanteAvecValeurNull_retourneFallback() {
        String cle = "test.string.null." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur(null).build();
        config.setActif(true);
        em.persist(config);
        em.flush();

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

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

    @Test
    @TestTransaction
    @DisplayName("getDecimal avec valeur non parseable retourne le fallback")
    void getDecimal_valeurNonParseable_retourneFallback() {
        String cle = "test.decimal.invalid." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("pas-un-nombre").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        BigDecimal result = defaultsService.getDecimal(cle, new BigDecimal("99.99"));

        assertThat(result).isEqualByComparingTo(new BigDecimal("99.99"));
    }

    @Test
    @TestTransaction
    @DisplayName("getDecimal avec valeur parseable retourne la valeur BigDecimal")
    void getDecimal_valeurParseable_retourneValeur() {
        String cle = "test.decimal.valid." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("1234.56").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        BigDecimal result = defaultsService.getDecimal(cle, BigDecimal.ZERO);

        assertThat(result).isEqualByComparingTo(new BigDecimal("1234.56"));
    }

    @Test
    @TestTransaction
    @DisplayName("getInt avec valeur non parseable retourne le fallback")
    void getInt_valeurNonParseable_retourneFallback() {
        String cle = "test.int.invalid." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("pas-un-entier").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        int result = defaultsService.getInt(cle, 42);

        assertThat(result).isEqualTo(42);
    }

    @Test
    @TestTransaction
    @DisplayName("getInt avec valeur parseable retourne la valeur entière")
    void getInt_valeurParseable_retourneValeur() {
        String cle = "test.int.valid." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("7").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        int result = defaultsService.getInt(cle, 0);

        assertThat(result).isEqualTo(7);
    }

    @Test
    @TestTransaction
    @DisplayName("getBoolean avec valeur 'true' retourne true")
    void getBoolean_valeurTrue_retourneTrue() {
        String cle = "test.bool.true." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("true").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        boolean result = defaultsService.getBoolean(cle, false);

        assertThat(result).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("getBoolean avec valeur 'false' retourne false")
    void getBoolean_valeurFalse_retourneFalse() {
        String cle = "test.bool.false." + System.currentTimeMillis();
        Configuration config = Configuration.builder().cle(cle).valeur("false").build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        boolean result = defaultsService.getBoolean(cle, true);

        assertThat(result).isFalse();
    }

    // =========================================================================
    // getString:135 — couverture explicite du try block (7I, 0B)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("getString avec clé existante et valeur valide → retourne la valeur")
    void getString_couvreL135_tryBlock_avecCleExistanteEtValeurValide() {
        String cle = "test.l135.valide." + System.currentTimeMillis();
        Configuration config = Configuration.builder()
                .cle(cle)
                .valeur("valeur-l135")
                .type("STRING")
                .build();
        config.setActif(true);
        em.persist(config);
        em.flush();

        // Appel direct à getString — couvre la ligne 135 (tryBlock + findByCle)
        // et les instructions suivantes (isPresent, getValeur, isBlank, return)
        String result = defaultsService.getString(cle, "fallback-non-attendu");

        assertThat(result).isEqualTo("valeur-l135");
    }

    @Test
    @TestTransaction
    @DisplayName("getString:135 — try block exécuté avec clé existante et valeur en blanc (fallback retourné)")
    void getString_couvreL135_tryBlock_avecValeurEnBlanc() {
        String cle = "test.l135.blanc." + System.currentTimeMillis();
        Configuration config = Configuration.builder()
                .cle(cle)
                .valeur("   ")
                .type("STRING")
                .build();
        config.setActif(true);
        em.persist(config);
        em.flush();

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

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

    @Test
    @DisplayName("getString:135 — try block avec exception ConfigRepo → fallback retourné")
    void getString_couvreL135_tryBlock_avecException() {
        // Appel avec une clé qui ne causera pas d'exception mais teste le chemin try→fallback
        // quand aucune donnée n'est en base (Optional.empty → fallback)
        String cle = "test.l135.exception." + System.currentTimeMillis();

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

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