package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.cotisation.request.CreateCotisationRequest;
import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.CotisationRepository;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.AfterEach;
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;

@QuarkusTest
class ExportServiceTest {

    @Inject
    ExportService exportService;

    @Inject
    CotisationService cotisationService;

    @Inject
    MembreService membreService;

    @Inject
    OrganisationService organisationService;

    @Inject
    CotisationRepository cotisationRepository;

    private CotisationResponse testCotisation;
    private CotisationResponse testCotisation2;

    @AfterEach
    @Transactional
    void cleanup() {
        if (testCotisation != null) {
            cotisationRepository.deleteById(testCotisation.getId());
        }
        if (testCotisation2 != null) {
            cotisationRepository.deleteById(testCotisation2.getId());
        }
    }

    @BeforeEach
    void setup() {
        Organisation org = new Organisation();
        org.setNom("Club Export " + UUID.randomUUID());
        org.setEmail("export-" + UUID.randomUUID() + "@test.com");
        org.setTypeOrganisation("CLUB");
        org.setStatut("ACTIVE");
        org.setActif(true);
        organisationService.creerOrganisation(org, "admin@test.com");

        Membre m = new Membre();
        m.setPrenom("Jean");
        m.setNom("Export");
        m.setEmail("jean.exp-" + UUID.randomUUID() + "@test.com");
        m.setNumeroMembre("M-" + UUID.randomUUID().toString().substring(0, 8));
        m.setDateNaissance(LocalDate.of(1990, 1, 1));
        m.setStatutCompte("ACTIF");
        m.setActif(true);
        membreService.creerMembre(m);

        testCotisation = cotisationService.createCotisation(
                CreateCotisationRequest.builder()
                        .membreId(m.getId())
                        .organisationId(org.getId())
                        .typeCotisation("ANNUELLE")
                        .libelle("Cotisation Export Test")
                        .montantDu(new BigDecimal("10000"))
                        .codeDevise("XOF")
                        .dateEcheance(LocalDate.now())
                        .periode("2024")
                        .annee(2024)
                        .build());

        testCotisation2 = cotisationService.createCotisation(
                CreateCotisationRequest.builder()
                        .membreId(m.getId())
                        .organisationId(org.getId())
                        .typeCotisation("MENSUELLE")
                        .libelle("Cotisation Export Test 2")
                        .montantDu(new BigDecimal("5000"))
                        .codeDevise("XOF")
                        .dateEcheance(LocalDate.now().plusMonths(1))
                        .periode("2024-02")
                        .annee(2024)
                        .build());
    }

    @Test
    @TestTransaction
    @DisplayName("exporterCotisationsCSV génère un contenu non vide")
    void exporterCotisationsCSV_returnsContent() {
        byte[] csv = exportService.exporterCotisationsCSV(List.of(testCotisation.getId()));
        assertThat(csv).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecuPaiement génère un contenu non vide")
    void genererRecuPaiement_returnsContent() {
        byte[] recu = exportService.genererRecuPaiement(testCotisation.getId());
        assertThat(recu).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRapportMensuel génère un rapport")
    void genererRapportMensuel_returnsContent() {
        byte[] rapport = exportService.genererRapportMensuel(LocalDate.now().getYear(), LocalDate.now().getMonthValue(),
                null);
        assertThat(rapport).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("exporterToutesCotisationsCSV avec statut filtre les cotisations")
    void exporterToutesCotisationsCSV_avecStatut_filtreCorrectement() {
        byte[] csv = exportService.exporterToutesCotisationsCSV("EN_ATTENTE", null, null);
        assertThat(csv).isNotEmpty();
        // Le header CSV est toujours présent même si aucune cotisation n'est filtrée
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("Numéro Référence");
    }

    @Test
    @TestTransaction
    @DisplayName("exporterToutesCotisationsCSV avec type filtre les cotisations")
    void exporterToutesCotisationsCSV_avecType_filtreCorrectement() {
        byte[] csv = exportService.exporterToutesCotisationsCSV(null, "MENSUELLE", null);
        assertThat(csv).isNotEmpty();
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("Numéro Référence");
    }

    @Test
    @TestTransaction
    @DisplayName("exporterToutesCotisationsCSV sans filtre retourne toutes les cotisations")
    void exporterToutesCotisationsCSV_sansFiltre_retourneTout() {
        byte[] csv = exportService.exporterToutesCotisationsCSV(null, null, null);
        assertThat(csv).isNotEmpty();
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("Numéro Référence");
    }

    @Test
    @TestTransaction
    @DisplayName("exporterToutesCotisationsCSV avec statut et type combinés filtre correctement")
    void exporterToutesCotisationsCSV_avecStatutEtType_filtreCorrectement() {
        byte[] csv = exportService.exporterToutesCotisationsCSV("PAYEE", "ANNUELLE", null);
        assertThat(csv).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecuPaiement retourne message erreur pour cotisation inexistante")
    void genererRecuPaiement_cotisationInexistante_retourneMessageErreur() {
        byte[] recu = exportService.genererRecuPaiement(UUID.randomUUID());
        assertThat(recu).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecusGroupes génère un reçu pour une cotisation")
    void genererRecusGroupes_avecUneCotisation_genereUnRecu() {
        byte[] recus = exportService.genererRecusGroupes(List.of(testCotisation.getId()));
        assertThat(recus).isNotEmpty();
        String contenu = new String(recus, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("REÇU DE PAIEMENT");
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecusGroupes avec deux cotisations génère le séparateur PAGE SUIVANTE")
    void genererRecusGroupes_avecDeuxCotisations_genereSeparateur() {
        byte[] recus = exportService.genererRecusGroupes(List.of(testCotisation.getId(), testCotisation2.getId()));
        assertThat(recus).isNotEmpty();
        String contenu = new String(recus, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("REÇU DE PAIEMENT");
        assertThat(contenu).contains("PAGE SUIVANTE");
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecusGroupes avec liste vide retourne tableau vide")
    void genererRecusGroupes_listeVide_retourneTableauVide() {
        byte[] recus = exportService.genererRecusGroupes(List.of());
        assertThat(recus).isNotNull();
        assertThat(recus).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRecuPaiementPDF délègue à genererRecuPaiement")
    void genererRecuPaiementPDF_delegueAGenererRecu() {
        byte[] recuPdf = exportService.genererRecuPaiementPDF(testCotisation.getId());
        byte[] recu = exportService.genererRecuPaiement(testCotisation.getId());
        // Les deux doivent retourner un contenu non vide
        assertThat(recuPdf).isNotEmpty();
        assertThat(recu).isNotEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("genererRapportMensuelPDF délègue à genererRapportMensuel")
    void genererRapportMensuelPDF_delegueAGenererRapport() {
        byte[] rapportPdf = exportService.genererRapportMensuelPDF(
                LocalDate.now().getYear(), LocalDate.now().getMonthValue(), null);
        assertThat(rapportPdf).isNotEmpty();
        String contenu = new String(rapportPdf, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("RAPPORT MENSUEL");
    }

    @Test
    @TestTransaction
    @DisplayName("exporterCotisationsCSV avec ID inexistant ignore la cotisation")
    void exporterCotisationsCSV_avecIdInexistant_ignoreLaCotisation() {
        byte[] csv = exportService.exporterCotisationsCSV(List.of(UUID.randomUUID()));
        assertThat(csv).isNotEmpty();
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);
        // Header présent mais aucune ligne de données
        assertThat(contenu).contains("Numéro Référence");
    }

    @Test
    @TestTransaction
    @DisplayName("genererRapportMensuel avec cotisation PAYEE déclenche le calcul du montantPaye (lambda ligne 199)")
    void genererRapportMensuel_avecCotisationPayee_calculeMontantPaye() {
        // Enregistrer le paiement complet pour que la cotisation passe à PAYEE
        cotisationService.enregistrerPaiement(
                testCotisation.getId(),
                new BigDecimal("10000"), // montant égal au montantDu → statut PAYEE
                LocalDate.now(),
                "CASH",
                "REF-RAPPORT-001"
        );

        // Générer le rapport du mois courant : la cotisation PAYEE doit être incluse
        // et déclenche la lambda .map(c -> c.getMontantPaye() != null ? ... : ZERO) à ligne 199
        byte[] rapport = exportService.genererRapportMensuel(
                LocalDate.now().getYear(),
                LocalDate.now().getMonthValue(),
                null);
        assertThat(rapport).isNotEmpty();
        String contenu = new String(rapport, java.nio.charset.StandardCharsets.UTF_8);
        assertThat(contenu).contains("RAPPORT MENSUEL");
    }

    @Test
    @TestTransaction
    @DisplayName("genererRapportMensuel avec cotisation PARTIELLEMENT_PAYEE déclenche le calcul montantPaye")
    void genererRapportMensuel_avecCotisationPartiellemntPayee_calculeMontantPaye() {
        // Paiement partiel → statut PARTIELLEMENT_PAYEE
        cotisationService.enregistrerPaiement(
                testCotisation2.getId(),
                new BigDecimal("2000"),
                LocalDate.now(),
                "CASH",
                "REF-RAPPORT-002"
        );

        byte[] rapport = exportService.genererRapportMensuel(
                LocalDate.now().getYear(),
                LocalDate.now().getMonthValue(),
                null);
        assertThat(rapport).isNotEmpty();
    }
}
