package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.comptabilite.request.*;
import dev.lions.unionflow.server.api.dto.comptabilite.response.*;
import dev.lions.unionflow.server.api.dto.paiement.request.CreatePaiementRequest;
import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;

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

@QuarkusTest
class ComptabiliteServiceTest {

    @Inject
    ComptabiliteService comptabiliteService;

    @Inject
    OrganisationService organisationService;

    @Inject
    MembreService membreService;

    @Inject
    PaiementService paiementService;

    private Organisation testOrganisation;

    @BeforeEach
    void setup() {
        testOrganisation = new Organisation();
        testOrganisation.setNom("Organisation Compta " + UUID.randomUUID());
        testOrganisation.setEmail("org-compta-" + UUID.randomUUID() + "@test.com");
        testOrganisation.setTypeOrganisation("ASSOCIATION");
        testOrganisation.setStatut("ACTIVE");
        testOrganisation.setActif(true);
        organisationService.creerOrganisation(testOrganisation, "admin@test.com");
    }

    // =========================================================================
    // creerCompteComptable
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerCompteComptable crée un compte valide")
    void creerCompteComptable_validRequest_createsCompte() {
        String numCompte = "512" + UUID.randomUUID().toString().substring(0, 5);
        CreateCompteComptableRequest request = new CreateCompteComptableRequest(
                numCompte,
                "Banque Test",
                TypeCompteComptable.ACTIF,
                5,
                BigDecimal.ZERO,
                BigDecimal.ZERO,
                false,
                false,
                "Description banque");

        CompteComptableResponse response = comptabiliteService.creerCompteComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getNumeroCompte()).isEqualTo(numCompte);
        assertThat(response.getLibelle()).isEqualTo("Banque Test");
    }

    @Test
    @TestTransaction
    @DisplayName("creerCompteComptable lève IllegalArgumentException si le numéro existe déjà")
    void creerCompteComptable_duplicateNumero_throwsIllegalArgument() {
        String numCompte = "DUP-" + UUID.randomUUID().toString().substring(0, 5);
        CreateCompteComptableRequest request = new CreateCompteComptableRequest(
                numCompte, "Compte Dup", TypeCompteComptable.ACTIF, 5,
                BigDecimal.ZERO, BigDecimal.ZERO, false, false, "");

        comptabiliteService.creerCompteComptable(request);

        assertThatThrownBy(() -> comptabiliteService.creerCompteComptable(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining(numCompte);
    }

    // =========================================================================
    // trouverCompteParId
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("trouverCompteParId retourne le compte si l'ID existe")
    void trouverCompteParId_existingId_returnsCompte() {
        String numCompte = "TR-" + UUID.randomUUID().toString().substring(0, 5);
        CompteComptableResponse created = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest(numCompte, "Compte TR",
                        TypeCompteComptable.PASSIF, 4,
                        BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));

        CompteComptableResponse found = comptabiliteService.trouverCompteParId(created.getId());

        assertThat(found).isNotNull();
        assertThat(found.getNumeroCompte()).isEqualTo(numCompte);
    }

    @Test
    @TestTransaction
    @DisplayName("trouverCompteParId lève NotFoundException si l'ID est inconnu")
    void trouverCompteParId_unknownId_throwsNotFound() {
        UUID unknownId = UUID.randomUUID();

        assertThatThrownBy(() -> comptabiliteService.trouverCompteParId(unknownId))
                .isInstanceOf(NotFoundException.class);
    }

    // =========================================================================
    // listerTousLesComptes
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("listerTousLesComptes retourne la liste des comptes actifs")
    void listerTousLesComptes_returnsNonNullList() {
        // Create at least one account to ensure the list is populated
        String numCompte = "LS-" + UUID.randomUUID().toString().substring(0, 5);
        comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest(numCompte, "Compte LS",
                        TypeCompteComptable.ACTIF, 5,
                        BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));

        List<CompteComptableResponse> comptes = comptabiliteService.listerTousLesComptes();

        assertThat(comptes).isNotNull();
        assertThat(comptes).isNotEmpty();
        assertThat(comptes).anyMatch(c -> c.getNumeroCompte().equals(numCompte));
    }

    // =========================================================================
    // creerJournalComptable
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerJournalComptable crée un journal valide")
    void creerJournalComptable_validRequest_createsJournal() {
        String code = "BQ" + UUID.randomUUID().toString().substring(0, 3);
        CreateJournalComptableRequest request = new CreateJournalComptableRequest(
                code,
                "Journal Banque",
                TypeJournalComptable.BANQUE,
                LocalDate.now(),
                null,
                "OUVERT",
                "Description journal");

        JournalComptableResponse response = comptabiliteService.creerJournalComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getCode()).isEqualTo(code);
    }

    @Test
    @TestTransaction
    @DisplayName("creerJournalComptable lève IllegalArgumentException si le code existe déjà")
    void creerJournalComptable_duplicateCode_throwsIllegalArgument() {
        String code = "DUP-J-" + UUID.randomUUID().toString().substring(0, 3);
        CreateJournalComptableRequest request = new CreateJournalComptableRequest(
                code, "Journal Dup", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", "");

        comptabiliteService.creerJournalComptable(request);

        assertThatThrownBy(() -> comptabiliteService.creerJournalComptable(request))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining(code);
    }

    @Test
    @TestTransaction
    @DisplayName("creerJournalComptable utilise 'OUVERT' par défaut si statut est null")
    void creerJournalComptable_nullStatut_usesDefaultOuvert() {
        String code = "NS-" + UUID.randomUUID().toString().substring(0, 3);
        CreateJournalComptableRequest request = new CreateJournalComptableRequest(
                code, "Journal NS", TypeJournalComptable.VENTES,
                LocalDate.now(), null, null, ""); // null statut

        JournalComptableResponse response = comptabiliteService.creerJournalComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getStatut()).isEqualTo("OUVERT");
    }

    // =========================================================================
    // trouverJournalParId
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("trouverJournalParId retourne le journal si l'ID existe")
    void trouverJournalParId_existingId_returnsJournal() {
        String code = "TJ-" + UUID.randomUUID().toString().substring(0, 3);
        JournalComptableResponse created = comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(code, "Journal TJ",
                        TypeJournalComptable.ACHATS, LocalDate.now(), null, "OUVERT", ""));

        JournalComptableResponse found = comptabiliteService.trouverJournalParId(created.getId());

        assertThat(found).isNotNull();
        assertThat(found.getCode()).isEqualTo(code);
    }

    @Test
    @TestTransaction
    @DisplayName("trouverJournalParId lève NotFoundException si l'ID est inconnu")
    void trouverJournalParId_unknownId_throwsNotFound() {
        assertThatThrownBy(() -> comptabiliteService.trouverJournalParId(UUID.randomUUID()))
                .isInstanceOf(NotFoundException.class);
    }

    // =========================================================================
    // listerTousLesJournaux
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("listerTousLesJournaux retourne une liste non nulle")
    void listerTousLesJournaux_returnsNonNullList() {
        String code = "LJ-" + UUID.randomUUID().toString().substring(0, 3);
        comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(code, "Journal LJ",
                        TypeJournalComptable.CAISSE, LocalDate.now(), null, "OUVERT", ""));

        List<JournalComptableResponse> journaux = comptabiliteService.listerTousLesJournaux();

        assertThat(journaux).isNotNull().isNotEmpty();
        assertThat(journaux).anyMatch(j -> j.getCode().equals(code));
    }

    // =========================================================================
    // creerEcritureComptable
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable valide l'équilibre débit/crédit")
    void creerEcritureComptable_unbalanced_throwsException() {
        CompteComptableResponse compte = comptabiliteService.creerCompteComptable(new CreateCompteComptableRequest(
                "PC-" + UUID.randomUUID().toString().substring(0, 5), "Compte", TypeCompteComptable.ACTIF, 3,
                BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));

        JournalComptableResponse journal = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "J-" + UUID.randomUUID().toString().substring(0, 3), "Journal", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        CreateLigneEcritureRequest ligne1 = new CreateLigneEcritureRequest(
                1, new BigDecimal("100"), BigDecimal.ZERO, "Debit", "REF1", null, compte.getId());
        CreateLigneEcritureRequest ligne2 = new CreateLigneEcritureRequest(
                2, BigDecimal.ZERO, new BigDecimal("50"), "Credit", "REF2", null, compte.getId());

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-001", LocalDate.now(), "Achat déséquilibré", "REF-EXT", null, false,
                new BigDecimal("100"), new BigDecimal("50"), "Com",
                journal.getId(), testOrganisation.getId(), null, List.of(ligne1, ligne2));

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(IllegalArgumentException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec équilibre crée l'écriture")
    void creerEcritureComptable_balanced_createsEcriture() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("C1-" + UUID.randomUUID().toString().substring(0, 5), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("C2-" + UUID.randomUUID().toString().substring(0, 5), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "J-" + UUID.randomUUID().toString().substring(0, 3), "J", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("1000"), BigDecimal.ZERO, "Debit", "R1",
                        null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("1000"), "Credit", "R2",
                        null, c2.getId()));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-002", LocalDate.now(), "Achat équilibré", "REF-EXT", null, false,
                new BigDecimal("1000"), new BigDecimal("1000"), "",
                j.getId(), testOrganisation.getId(), null, lignes);

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);
        assertThat(response).isNotNull();
        assertThat(response.getMontantDebit()).isEqualByComparingTo("1000");
        assertThat(response.getMontantCredit()).isEqualByComparingTo("1000");
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable lève IllegalArgumentException si les lignes sont null ou vides")
    void creerEcritureComptable_nullLignes_throwsIllegalArgument() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JN-" + UUID.randomUUID().toString().substring(0, 3), "JNull", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-NULL", LocalDate.now(), "Écriture sans lignes", null, null, false,
                BigDecimal.ZERO, BigDecimal.ZERO, "",
                j.getId(), testOrganisation.getId(), null, null); // null lignes

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(IllegalArgumentException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable lève NotFoundException si le journal n'existe pas")
    void creerEcritureComptable_unknownJournal_throwsNotFound() {
        CompteComptableResponse c = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("CNF-" + UUID.randomUUID().toString().substring(0, 5), "C",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("500"), BigDecimal.ZERO, "D", "R1", null, c.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("500"), "C", "R2", null, c.getId()));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-NFJ", LocalDate.now(), "Écriture journal inconnu", null, null, false,
                new BigDecimal("500"), new BigDecimal("500"), "",
                UUID.randomUUID(), // unknown journal ID
                testOrganisation.getId(), null, lignes);

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable lève NotFoundException si l'organisation n'existe pas")
    void creerEcritureComptable_unknownOrganisation_throwsNotFound() {
        CompteComptableResponse c = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("CNO-" + UUID.randomUUID().toString().substring(0, 5), "C",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JO-" + UUID.randomUUID().toString().substring(0, 3), "J", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("200"), BigDecimal.ZERO, "D", "R1", null, c.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("200"), "C", "R2", null, c.getId()));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-NFO", LocalDate.now(), "Écriture org inconnue", null, null, false,
                new BigDecimal("200"), new BigDecimal("200"), "",
                j.getId(),
                UUID.randomUUID(), // unknown organisation ID
                null, lignes);

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable lève NotFoundException si le compte comptable d'une ligne n'existe pas")
    void creerEcritureComptable_unknownCompteComptable_throwsNotFound() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JC-" + UUID.randomUUID().toString().substring(0, 3), "J", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("300"), BigDecimal.ZERO, "D", "R1", null,
                        UUID.randomUUID()), // unknown compte ID
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("300"), "C", "R2", null,
                        UUID.randomUUID())); // unknown compte ID

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-NFC", LocalDate.now(), "Écriture compte inconnu", null, null, false,
                new BigDecimal("300"), new BigDecimal("300"), "",
                j.getId(), testOrganisation.getId(), null, lignes);

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec dateEcriture null utilise la date du jour")
    void creerEcritureComptable_nullDate_usesToday() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("CD1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("CD2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JD-" + UUID.randomUUID().toString().substring(0, 3), "J", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("400"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("400"), "C", "R2", null, c2.getId()));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-DATE", null, "Écriture date null", null, null, false,
                new BigDecimal("400"), new BigDecimal("400"), "",
                j.getId(), testOrganisation.getId(), null, lignes);

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);
        assertThat(response).isNotNull();
        assertThat(response.getDateEcriture()).isEqualTo(LocalDate.now());
    }

    // =========================================================================
    // trouverEcritureParId
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("trouverEcritureParId retourne l'écriture si l'ID existe")
    void trouverEcritureParId_existingId_returnsEcriture() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("TE1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("TE2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JE-" + UUID.randomUUID().toString().substring(0, 3), "J", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        EcritureComptableResponse created = comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest("PIECE-TE", LocalDate.now(), "Test écriture", null, null, false,
                        new BigDecimal("600"), new BigDecimal("600"), "",
                        j.getId(), testOrganisation.getId(), null,
                        List.of(
                                new CreateLigneEcritureRequest(1, new BigDecimal("600"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("600"), "C", "R2", null, c2.getId()))));

        EcritureComptableResponse found = comptabiliteService.trouverEcritureParId(created.getId());

        assertThat(found).isNotNull();
        assertThat(found.getNumeroPiece()).isEqualTo("PIECE-TE");
    }

    @Test
    @TestTransaction
    @DisplayName("trouverEcritureParId lève NotFoundException si l'ID est inconnu")
    void trouverEcritureParId_unknownId_throwsNotFound() {
        assertThatThrownBy(() -> comptabiliteService.trouverEcritureParId(UUID.randomUUID()))
                .isInstanceOf(NotFoundException.class);
    }

    // =========================================================================
    // listerEcrituresParJournal
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("listerEcrituresParJournal retourne les écritures du journal")
    void listerEcrituresParJournal_returnsEcritures() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("LPJ1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("LPJ2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JL-" + UUID.randomUUID().toString().substring(0, 3), "JL", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest("PIECE-LPJ", LocalDate.now(), "Test liste par journal",
                        null, null, false, new BigDecimal("750"), new BigDecimal("750"), "",
                        j.getId(), testOrganisation.getId(), null,
                        List.of(
                                new CreateLigneEcritureRequest(1, new BigDecimal("750"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("750"), "C", "R2", null, c2.getId()))));

        List<EcritureComptableResponse> ecritures = comptabiliteService.listerEcrituresParJournal(j.getId());

        assertThat(ecritures).isNotNull().isNotEmpty();
        assertThat(ecritures).anyMatch(e -> e.getNumeroPiece().equals("PIECE-LPJ"));
    }

    // =========================================================================
    // listerEcrituresParOrganisation
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("listerEcrituresParJournal retourne liste vide pour journal sans écritures")
    void listerEcrituresParJournal_emptyJournal_returnsEmptyList() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JV-" + UUID.randomUUID().toString().substring(0, 3), "Journal Vide",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        List<EcritureComptableResponse> ecritures = comptabiliteService.listerEcrituresParJournal(j.getId());

        assertThat(ecritures).isNotNull().isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("listerEcrituresParOrganisation retourne liste vide pour organisation sans écritures")
    void listerEcrituresParOrganisation_emptyOrg_returnsEmptyList() {
        Organisation emptyOrg = new Organisation();
        emptyOrg.setNom("Org Vide Compta " + UUID.randomUUID());
        emptyOrg.setEmail("org-vide-" + UUID.randomUUID() + "@test.com");
        emptyOrg.setTypeOrganisation("ASSOCIATION");
        emptyOrg.setStatut("ACTIVE");
        emptyOrg.setActif(true);
        organisationService.creerOrganisation(emptyOrg, "admin@test.com");

        List<EcritureComptableResponse> ecritures =
                comptabiliteService.listerEcrituresParOrganisation(emptyOrg.getId());

        assertThat(ecritures).isNotNull().isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("creerCompteComptable avec soldeInitial null utilise ZERO")
    void creerCompteComptable_nullSoldeInitial_usesZero() {
        String numCompte = "NI-" + UUID.randomUUID().toString().substring(0, 5);
        CreateCompteComptableRequest request = new CreateCompteComptableRequest(
                numCompte, "Compte NI", TypeCompteComptable.ACTIF, 5,
                null, null, null, null, ""); // null soldeInitial, soldeActuel, collectif, analytique

        CompteComptableResponse response = comptabiliteService.creerCompteComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getSoldeInitial()).isEqualByComparingTo(BigDecimal.ZERO);
    }

    // =========================================================================
    // listerEcrituresParOrganisation
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("listerEcrituresParOrganisation retourne les écritures de l'organisation")
    void listerEcrituresParOrganisation_returnsEcritures() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("LPO1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("LPO2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JO-" + UUID.randomUUID().toString().substring(0, 3), "JO", TypeJournalComptable.OD,
                LocalDate.now(), null, "OUVERT", ""));

        comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest("PIECE-LPO", LocalDate.now(), "Test liste par org",
                        null, null, false, new BigDecimal("850"), new BigDecimal("850"), "",
                        j.getId(), testOrganisation.getId(), null,
                        List.of(
                                new CreateLigneEcritureRequest(1, new BigDecimal("850"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("850"), "C", "R2", null, c2.getId()))));

        List<EcritureComptableResponse> ecritures =
                comptabiliteService.listerEcrituresParOrganisation(testOrganisation.getId());

        assertThat(ecritures).isNotNull().isNotEmpty();
        assertThat(ecritures).anyMatch(e -> e.getNumeroPiece().equals("PIECE-LPO"));
    }

    // =========================================================================
    // creerEcritureComptable — paiementId not null (L405-410 in ComptabiliteService)
    // Covers convertToEntity branch: if (dto.paiementId() != null)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec paiementId valide lie le paiement à l'écriture")
    void creerEcritureComptable_withPaiementId_linksPaiement() {
        // Create prerequisite compte and journal
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("PAY1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("PAY2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JP-" + UUID.randomUUID().toString().substring(0, 3), "J Paiement",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        // Create a Membre then a Paiement
        Membre membre = new Membre();
        membre.setPrenom("Test");
        membre.setNom("Paiement");
        membre.setEmail("pay-" + UUID.randomUUID() + "@test.com");
        membre.setNumeroMembre("MP-" + UUID.randomUUID().toString().substring(0, 8));
        membre.setDateNaissance(java.time.LocalDate.of(1990, 1, 1));
        membre.setStatutCompte("ACTIF");
        membre.setActif(true);
        membreService.creerMembre(membre);

        PaiementResponse paiement = paiementService.creerPaiement(CreatePaiementRequest.builder()
                .numeroReference("PAY-REF-" + UUID.randomUUID().toString().substring(0, 8))
                .montant(new BigDecimal("1000"))
                .codeDevise("XOF")
                .methodePaiement("ESPECES")
                .commentaire("Test paiement pour écriture")
                .membreId(membre.getId())
                .build());

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("1000"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("1000"), "C", "R2", null, c2.getId()));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-PAY", LocalDate.now(), "Écriture avec paiement", null, null, null,
                new BigDecimal("1000"), new BigDecimal("1000"), "",
                j.getId(), testOrganisation.getId(), paiement.getId(), lignes);

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getPaiementId()).isEqualTo(paiement.getId());
    }

    // =========================================================================
    // creerEcritureComptable — empty lignes list (L230 isEmpty() branch)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec liste de lignes vide lève IllegalArgumentException")
    void creerEcritureComptable_emptyLignes_throwsIllegalArgument() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JE2-" + UUID.randomUUID().toString().substring(0, 3), "J Empty Lignes",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-EL", LocalDate.now(), "Écriture lignes vides", null, null, false,
                BigDecimal.ZERO, BigDecimal.ZERO, "",
                j.getId(), testOrganisation.getId(), null, List.of()); // empty list, not null

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(IllegalArgumentException.class);
    }

    // =========================================================================
    // creerEcritureComptable — pointe null branch (L384: dto.pointe() != null → false)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec pointe null utilise false par défaut")
    void creerEcritureComptable_nullPointe_usesFalse() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("PN1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("PN2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JPN-" + UUID.randomUUID().toString().substring(0, 3), "J Pointe Null",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("700"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("700"), "C", "R2", null, c2.getId()));

        // pointe = null → triggers the false branch in: dto.pointe() != null ? dto.pointe() : false
        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-PN", LocalDate.now(), "Écriture pointe null", null, null, null,
                new BigDecimal("700"), new BigDecimal("700"), "",
                j.getId(), null, null, lignes);  // organisationId=null triggers the null branch for organisationId too

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getPointe()).isFalse();
    }

    // =========================================================================
    // creerEcritureComptable — lignes avec montantDebit null et montantCredit null
    // (L461-L462 in convertToEntity for LigneEcriture)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec montantDebit et montantCredit null dans lignes utilise ZERO")
    void creerEcritureComptable_nullMontantInLignes_usesZero() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NM1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NM2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JNM-" + UUID.randomUUID().toString().substring(0, 3), "J Null Montant",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        // montantDebit=null and montantCredit=null — these go to ZERO default
        // For the écriture to be équilibrée, both totals must equal 0
        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, null, null, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, null, null, "C", "R2", null, c2.getId()));

        // isEcritureEquilibree: totalDebit=0 == totalCredit=0 → true (balanced)
        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-NM", LocalDate.now(), "Écriture montants null", null, null, false,
                BigDecimal.ZERO, BigDecimal.ZERO, "",
                j.getId(), null, null, lignes);

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);

        assertThat(response).isNotNull();
        assertThat(response.getLignes()).hasSize(2);
        assertThat(response.getLignes().get(0).getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(response.getLignes().get(0).getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
    }

    // =========================================================================
    // creerEcritureComptable — lignes avec compteComptableId null (L467 false branch)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable avec compteComptableId null dans lignes ne plante pas")
    void creerEcritureComptable_nullCompteComptableInLignes_works() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(new CreateJournalComptableRequest(
                "JCC-" + UUID.randomUUID().toString().substring(0, 3), "J CompteNull",
                TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        // compteComptableId=null → skip compte lookup (L467 false branch)
        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, null, null, "D", "R1", null, null),
                new CreateLigneEcritureRequest(2, null, null, "C", "R2", null, null));

        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-CC", LocalDate.now(), "Écriture comptes null", null, null, false,
                BigDecimal.ZERO, BigDecimal.ZERO, "",
                j.getId(), null, null, lignes);

        EcritureComptableResponse response = comptabiliteService.creerEcritureComptable(request);

        assertThat(response).isNotNull();
        // compteComptableId is null, so it should be null in response
        assertThat(response.getLignes().get(0).getCompteComptableId()).isNull();
    }

    // =========================================================================
    // convertToEntity$7 — lambda orElseThrow pour paiement introuvable (line 409)
    // Couvre: paiementId != null mais paiement non trouvé → NotFoundException
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable lève NotFoundException si le paiementId n'existe pas")
    void creerEcritureComptable_unknownPaiementId_throwsNotFound() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("UPY1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("UPY2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(
                        "JUPY-" + UUID.randomUUID().toString().substring(0, 3), "J",
                        TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("500"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("500"), "C", "R2", null, c2.getId()));

        // paiementId non null mais inexistant → déclenche lambda$7 : orElseThrow → NotFoundException
        CreateEcritureComptableRequest request = new CreateEcritureComptableRequest(
                "PIECE-UPY", LocalDate.now(), "Écriture paiement inconnu", null, null, false,
                new BigDecimal("500"), new BigDecimal("500"), "",
                j.getId(), testOrganisation.getId(),
                UUID.randomUUID(), // paiementId inconnu → NotFoundException
                lignes);

        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(request))
                .isInstanceOf(NotFoundException.class);
    }

    // =========================================================================
    // convertToResponse(CompteComptable null) — null guard (line 249)
    // convertToEntity(CreateCompteComptableRequest null) — null guard (line 273)
    // convertToResponse(JournalComptable null) — null guard (line 293)
    // convertToEntity(CreateJournalComptableRequest null) — null guard (line 315)
    // convertToResponse(EcritureComptable null) — null guard (line 333)
    // convertToEntity(CreateEcritureComptableRequest null) — null guard (line 374)
    // convertToResponse(LigneEcriture null) — null guard (line 427)
    // convertToEntity(CreateLigneEcritureRequest null) — null guard (line 455)
    //
    // Les null guards dans les méthodes privées ne sont accessibles que via réflexion.
    // On teste chaque méthode privée directement.
    // =========================================================================

    @Test
    @DisplayName("convertToResponse(CompteComptable null) via réflexion retourne null")
    void convertToResponse_compteNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToResponse",
                dev.lions.unionflow.server.entity.CompteComptable.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToEntity(CreateCompteComptableRequest null) via réflexion retourne null")
    void convertToEntity_compteRequestNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToEntity",
                dev.lions.unionflow.server.api.dto.comptabilite.request.CreateCompteComptableRequest.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToResponse(JournalComptable null) via réflexion retourne null")
    void convertToResponse_journalNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToResponse",
                dev.lions.unionflow.server.entity.JournalComptable.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToEntity(CreateJournalComptableRequest null) via réflexion retourne null")
    void convertToEntity_journalRequestNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToEntity",
                dev.lions.unionflow.server.api.dto.comptabilite.request.CreateJournalComptableRequest.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToResponse(EcritureComptable null) via réflexion retourne null")
    void convertToResponse_ecritureNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToResponse",
                dev.lions.unionflow.server.entity.EcritureComptable.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToEntity(CreateEcritureComptableRequest null) via réflexion retourne null")
    void convertToEntity_ecritureRequestNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToEntity",
                dev.lions.unionflow.server.api.dto.comptabilite.request.CreateEcritureComptableRequest.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToResponse(LigneEcriture null) via réflexion retourne null")
    void convertToResponse_ligneNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToResponse",
                dev.lions.unionflow.server.entity.LigneEcriture.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    @Test
    @DisplayName("convertToEntity(CreateLigneEcritureRequest null) via réflexion retourne null")
    void convertToEntity_ligneRequestNull_returnsNull() throws Exception {
        ComptabiliteService actualService =
                (ComptabiliteService) ((io.quarkus.arc.ClientProxy) comptabiliteService).arc_contextualInstance();
        java.lang.reflect.Method method = ComptabiliteService.class.getDeclaredMethod(
                "convertToEntity",
                dev.lions.unionflow.server.api.dto.comptabilite.request.CreateLigneEcritureRequest.class);
        method.setAccessible(true);
        Object result = method.invoke(actualService, (Object) null);
        assertThat(result).isNull();
    }

    // =========================================================================
    // convertToResponse(EcritureComptable) — branches journal/organisation/paiement null (lines 349-358)
    // Ces branches sont couvertes quand l'écriture n'a pas de journal/org/paiement lié.
    // Le test creerEcritureComptable_nullPointe_usesFalse crée déjà une écriture sans org.
    // Ajoutons un test qui vérifie que listerEcrituresParJournal couvre la branche paiement null
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("trouverEcritureParId retourne écriture sans paiement avec paiementId null dans réponse")
    void trouverEcritureParId_sansPaiement_paiementIdNull() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NSP1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NSP2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(
                        "JNSP-" + UUID.randomUUID().toString().substring(0, 3), "J",
                        TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("900"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("900"), "C", "R2", null, c2.getId()));

        // Écriture sans paiementId (null) — branche if (ecriture.getPaiement() != null) → false
        EcritureComptableResponse created = comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest(
                        "PIECE-NSP", LocalDate.now(), "Écriture sans paiement", null, null, false,
                        new BigDecimal("900"), new BigDecimal("900"), "",
                        j.getId(), testOrganisation.getId(), null, lignes)); // paiementId=null

        EcritureComptableResponse found = comptabiliteService.trouverEcritureParId(created.getId());

        assertThat(found).isNotNull();
        assertThat(found.getPaiementId()).isNull(); // paiement est null → branche false couverte
    }

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable sans organisation (organisationId null) → organisationId null dans réponse (L333 L352 false)")
    void creerEcritureComptable_sansOrganisation_organisationIdNull() {
        CompteComptableResponse c1 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NSO1-" + UUID.randomUUID().toString().substring(0, 4), "C1",
                        TypeCompteComptable.ACTIF, 5, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        CompteComptableResponse c2 = comptabiliteService.creerCompteComptable(
                new CreateCompteComptableRequest("NSO2-" + UUID.randomUUID().toString().substring(0, 4), "C2",
                        TypeCompteComptable.PASSIF, 4, BigDecimal.ZERO, BigDecimal.ZERO, false, false, ""));
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(
                        "JNSO-" + UUID.randomUUID().toString().substring(0, 3), "J",
                        TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        List<CreateLigneEcritureRequest> lignes = List.of(
                new CreateLigneEcritureRequest(1, new BigDecimal("700"), BigDecimal.ZERO, "D", "R1", null, c1.getId()),
                new CreateLigneEcritureRequest(2, BigDecimal.ZERO, new BigDecimal("700"), "C", "R2", null, c2.getId()));

        // organisationId=null → branche L396 false → organisation non settée → L352 false → organisationId=null
        EcritureComptableResponse created = comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest(
                        "PIECE-NSO", LocalDate.now(), "Écriture sans organisation", null, null, false,
                        new BigDecimal("700"), new BigDecimal("700"), "",
                        j.getId(), null, null, lignes)); // organisationId=null

        EcritureComptableResponse found = comptabiliteService.trouverEcritureParId(created.getId());

        assertThat(found).isNotNull();
        assertThat(found.getOrganisationId()).isNull(); // organisation est null → L352 false couverte
    }

    // =========================================================================
    // L414: convertToEntity — dto.lignes() == null est dead code car isEcritureEquilibree()
    // retourne false pour lignes=null → IllegalArgumentException avant d'atteindre L414.
    // Le test ci-dessous documente ce comportement (lignes=null → exception écriture non équilibrée).
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("creerEcritureComptable sans lignes (lignes=null) → isEcritureEquilibree=false → IllegalArgumentException")
    void creerEcritureComptable_sansLignes_isEquilibreeFalse_throws() {
        JournalComptableResponse j = comptabiliteService.creerJournalComptable(
                new CreateJournalComptableRequest(
                        "JNOL-" + UUID.randomUUID().toString().substring(0, 3), "J sans lignes",
                        TypeJournalComptable.OD, LocalDate.now(), null, "OUVERT", ""));

        // lignes=null → isEcritureEquilibree returns false → throws before reaching L414
        assertThatThrownBy(() -> comptabiliteService.creerEcritureComptable(
                new CreateEcritureComptableRequest(
                        "PIECE-NL-" + UUID.randomUUID().toString().substring(0, 6),
                        LocalDate.now(), "Écriture sans lignes", null, null, false,
                        BigDecimal.ZERO, BigDecimal.ZERO, "",
                        j.getId(), testOrganisation.getId(), null, null))) // lignes=null
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("équilibrée");
    }
}
