package dev.lions.unionflow.server.entity;

import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;

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

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

@DisplayName("EcritureComptable")
class EcritureComptableTest {

    private static JournalComptable newJournal() {
        JournalComptable j = new JournalComptable();
        j.setId(UUID.randomUUID());
        j.setCode("BQ");
        j.setLibelle("Banque");
        j.setTypeJournal(TypeJournalComptable.BANQUE);
        return j;
    }

    @Test
    @DisplayName("getters/setters")
    void gettersSetters() {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("ECR-001");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("Virement");
        e.setReference("REF-1");
        e.setLettrage("L1");
        e.setPointe(true);
        e.setMontantDebit(new BigDecimal("100.00"));
        e.setMontantCredit(new BigDecimal("100.00"));
        e.setJournal(newJournal());

        assertThat(e.getNumeroPiece()).isEqualTo("ECR-001");
        assertThat(e.getLibelle()).isEqualTo("Virement");
        assertThat(e.getMontantDebit()).isEqualByComparingTo("100.00");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("100.00");
        assertThat(e.getPointe()).isTrue();
    }

    @Test
    @DisplayName("isEquilibree: true si débit = crédit")
    void isEquilibree() {
        EcritureComptable e = new EcritureComptable();
        e.setJournal(newJournal());
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("50.00"));
        e.setMontantCredit(new BigDecimal("50.00"));
        assertThat(e.isEquilibree()).isTrue();
        e.setMontantCredit(new BigDecimal("60.00"));
        assertThat(e.isEquilibree()).isFalse();
        e.setMontantDebit(null);
        assertThat(e.isEquilibree()).isFalse();
    }

    @Test
    @DisplayName("calculerTotaux: à partir des lignes")
    void calculerTotaux() {
        JournalComptable j = newJournal();
        EcritureComptable e = new EcritureComptable();
        e.setJournal(j);
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(BigDecimal.ZERO);
        e.setMontantCredit(BigDecimal.ZERO);
        LigneEcriture l1 = new LigneEcriture();
        l1.setMontantDebit(new BigDecimal("100"));
        l1.setMontantCredit(BigDecimal.ZERO);
        LigneEcriture l2 = new LigneEcriture();
        l2.setMontantDebit(BigDecimal.ZERO);
        l2.setMontantCredit(new BigDecimal("100"));
        e.getLignes().add(l1);
        e.getLignes().add(l2);
        e.calculerTotaux();
        assertThat(e.getMontantDebit()).isEqualByComparingTo("100");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("100");
    }

    @Test
    @DisplayName("genererNumeroPiece")
    void genererNumeroPiece() {
        String num = EcritureComptable.genererNumeroPiece("ECR", LocalDate.of(2025, 3, 15));
        assertThat(num).startsWith("ECR-20250315-");
    }

    @Test
    @DisplayName("equals et hashCode")
    void equalsHashCode() {
        UUID id = UUID.randomUUID();
        JournalComptable j = newJournal();
        EcritureComptable a = new EcritureComptable();
        a.setId(id);
        a.setNumeroPiece("N1");
        a.setDateEcriture(LocalDate.now());
        a.setLibelle("L");
        a.setJournal(j);
        EcritureComptable b = new EcritureComptable();
        b.setId(id);
        b.setNumeroPiece("N1");
        b.setDateEcriture(a.getDateEcriture());
        b.setLibelle("L");
        b.setJournal(j);
        assertThat(a).isEqualTo(b);
        assertThat(a.hashCode()).isEqualTo(b.hashCode());
    }

    @Test
    @DisplayName("toString non null")
    void toString_nonNull() {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setJournal(newJournal());
        assertThat(e.toString()).isNotNull().isNotEmpty();
    }

    @Test
    @DisplayName("onUpdate (PreUpdate) calcule les totaux via réflexion")
    void onUpdate_calculesTotaux() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setJournal(newJournal());
        e.setMontantDebit(BigDecimal.ZERO);
        e.setMontantCredit(BigDecimal.ZERO);

        LigneEcriture l1 = new LigneEcriture();
        l1.setMontantDebit(new BigDecimal("200"));
        l1.setMontantCredit(BigDecimal.ZERO);
        LigneEcriture l2 = new LigneEcriture();
        l2.setMontantDebit(BigDecimal.ZERO);
        l2.setMontantCredit(new BigDecimal("200"));
        e.getLignes().add(l1);
        e.getLignes().add(l2);

        Method onUpdate = EcritureComptable.class.getDeclaredMethod("onUpdate");
        onUpdate.setAccessible(true);
        onUpdate.invoke(e);

        assertThat(e.getMontantDebit()).isEqualByComparingTo("200");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("200");
    }

    @Test
    @DisplayName("onCreate: initialise les défauts si null")
    void onCreate_setsDefaults() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece(null);
        e.setDateEcriture(null);
        e.setMontantDebit(null);
        e.setMontantCredit(null);
        e.setPointe(null);

        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);

        assertThat(e.getNumeroPiece()).isNotNull().startsWith("ECR-");
        assertThat(e.getDateEcriture()).isEqualTo(LocalDate.now());
        assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(e.getPointe()).isFalse();
    }

    @Test
    @DisplayName("onCreate: numeroPiece vide → généré")
    void onCreate_numeroPieceEmpty_generated() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("");
        e.setDateEcriture(LocalDate.of(2025, 6, 1));

        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);

        assertThat(e.getNumeroPiece()).isNotEmpty().startsWith("ECR-20250601-");
    }

    @Test
    @DisplayName("onCreate: avec lignes → calculerTotaux appelé")
    void onCreate_withLignes_calculesTotaux() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        LigneEcriture l1 = new LigneEcriture();
        l1.setMontantDebit(new BigDecimal("300"));
        l1.setMontantCredit(BigDecimal.ZERO);
        LigneEcriture l2 = new LigneEcriture();
        l2.setMontantDebit(BigDecimal.ZERO);
        l2.setMontantCredit(new BigDecimal("300"));
        e.getLignes().add(l1);
        e.getLignes().add(l2);

        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);

        assertThat(e.getMontantDebit()).isEqualByComparingTo("300");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("300");
    }

    @Test
    @DisplayName("isEquilibree: false si montantCredit null (branche ||)")
    void isEquilibree_montantCreditNull_returnsFalse() {
        // montantDebit non null mais montantCredit null → retourne false
        EcritureComptable e = new EcritureComptable();
        e.setJournal(newJournal());
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("100.00"));
        e.setMontantCredit(null);
        assertThat(e.isEquilibree()).isFalse();
    }

    @Test
    @DisplayName("calculerTotaux: lignes vides → totaux à ZERO")
    void calculerTotaux_emptyLignes_setsZero() {
        // Couvre la branche : `if (lignes == null || lignes.isEmpty()) { return ZERO; }`
        EcritureComptable e = new EcritureComptable();
        e.setJournal(newJournal());
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("500.00"));
        e.setMontantCredit(new BigDecimal("500.00"));
        // lignes est vide (défaut)
        e.calculerTotaux();
        assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
    }

    @Test
    @DisplayName("onCreate: lignes non vides présentes → calculerTotaux() appelé (branche true)")
    void onCreate_lignesNonVides_calculeTotaux() throws Exception {
        // Couvre la branche `if (lignes != null && !lignes.isEmpty())` → true dans onCreate
        // Ce test est complémentaire à onCreate_withLignes_calculesTotaux pour s'assurer
        // que le chemin est bien couvert.
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("PIECE-001");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("Test");
        LigneEcriture l1 = new LigneEcriture();
        l1.setMontantDebit(new BigDecimal("75"));
        l1.setMontantCredit(BigDecimal.ZERO);
        LigneEcriture l2 = new LigneEcriture();
        l2.setMontantDebit(BigDecimal.ZERO);
        l2.setMontantCredit(new BigDecimal("75"));
        e.getLignes().add(l1);
        e.getLignes().add(l2);

        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);

        assertThat(e.getMontantDebit()).isEqualByComparingTo("75");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("75");
        // numeroPiece déjà défini → conservé (branche false du if numeroPiece)
        assertThat(e.getNumeroPiece()).isEqualTo("PIECE-001");
    }

    @Test
    @DisplayName("calculerTotaux: lignes == null → totaux à ZERO (branche lignes null)")
    void calculerTotaux_nullLignes_setsZero() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("999"));
        e.setMontantCredit(new BigDecimal("999"));
        // Forcer lignes à null via réflexion
        java.lang.reflect.Field lignesField = EcritureComptable.class.getDeclaredField("lignes");
        lignesField.setAccessible(true);
        lignesField.set(e, null);
        e.calculerTotaux();
        assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
    }

    @Test
    @DisplayName("onCreate: lignes != null et isEmpty → calculerTotaux NON appelé (branche false)")
    void onCreate_lignesEmptyList_calculerTotauxNotCalled() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("10"));
        e.setMontantCredit(new BigDecimal("10"));
        // lignes est une ArrayList vide (valeur par défaut) → lignes != null && !lignes.isEmpty() → false
        assertThat(e.getLignes()).isEmpty();
        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);
        // calculerTotaux n'a pas été appelé → montants restent (ou reset à 0 par init)
        // but montantDebit/Credit were already set, and they won't be recalculated
        // Just verify no exception and onCreate ran
        assertThat(e.getNumeroPiece()).isEqualTo("X");
    }

    @Test
    @DisplayName("onCreate: numeroPiece vide ('') → génère un nouveau numeroPiece (branche isEmpty() true)")
    void onCreate_numeroPieceEmpty_generatesPiece() throws Exception {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece(""); // non-null mais vide → isEmpty() true → génère
        e.setDateEcriture(LocalDate.now()); // non-null → ternaire true
        e.setLibelle("L");
        e.setMontantDebit(new BigDecimal("10"));
        e.setMontantCredit(new BigDecimal("10"));

        Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
        onCreate.setAccessible(true);
        onCreate.invoke(e);

        // numeroPiece était vide → a été généré
        assertThat(e.getNumeroPiece()).isNotEmpty();
        assertThat(e.getNumeroPiece()).startsWith("ECR");
    }

    @Test
    @DisplayName("calculerTotaux: filtre les montants null (branch false des lambdas filter)")
    void calculerTotaux_withNullAmounts() {
        EcritureComptable e = new EcritureComptable();
        e.setNumeroPiece("X");
        e.setDateEcriture(LocalDate.now());
        e.setLibelle("L");
        // Ligne avec montants null → filtrée (couvre branch false: amount != null → false)
        LigneEcriture l1 = new LigneEcriture();
        l1.setMontantDebit(null);
        l1.setMontantCredit(null);
        LigneEcriture l2 = new LigneEcriture();
        l2.setMontantDebit(new BigDecimal("50"));
        l2.setMontantCredit(new BigDecimal("50"));
        e.getLignes().add(l1);
        e.getLignes().add(l2);
        e.calculerTotaux();
        assertThat(e.getMontantDebit()).isEqualByComparingTo("50");
        assertThat(e.getMontantCredit()).isEqualByComparingTo("50");
    }
}
