package dev.lions.unionflow.server.service;

import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.*;
import dev.lions.unionflow.server.api.enums.comptabilite.TypeCompteComptable;
import dev.lions.unionflow.server.entity.CompteComptable;
import dev.lions.unionflow.server.entity.EcritureComptable;
import dev.lions.unionflow.server.entity.LigneEcriture;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.CompteComptableRepository;
import dev.lions.unionflow.server.repository.EcritureComptableRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import lombok.extern.slf4j.Slf4j;

import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Génération des rapports comptables PDF SYSCOHADA révisé.
 *
 * <p>Rapports disponibles :
 * <ul>
 *   <li>Grand livre : détail de toutes les écritures par compte</li>
 *   <li>Balance générale : soldes débit/crédit/solde net par compte</li>
 *   <li>Compte de résultat : produits (classe 7+8) - charges (classe 6+8)</li>
 * </ul>
 */
@Slf4j
@ApplicationScoped
public class ComptabilitePdfService {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    private static final Color COLOR_HEADER = new Color(0x1A, 0x56, 0x8C);
    private static final Color COLOR_HEADER_TEXT = Color.WHITE;
    private static final Color COLOR_TOTAL_ROW = new Color(0xE8, 0xF0, 0xFE);
    private static final Color COLOR_ROW_ALT = new Color(0xF8, 0xFA, 0xFF);

    @Inject
    OrganisationRepository organisationRepository;

    @Inject
    CompteComptableRepository compteComptableRepository;

    @Inject
    EcritureComptableRepository ecritureComptableRepository;

    // ── Balance générale ─────────────────────────────────────────────────────

    /**
     * Génère la balance générale SYSCOHADA pour une organisation.
     * Liste tous les comptes avec cumul débit, cumul crédit et solde.
     */
    public byte[] genererBalance(UUID organisationId, LocalDate dateDebut, LocalDate dateFin) {
        Organisation org = getOrg(organisationId);
        List<CompteComptable> comptes = compteComptableRepository.findByOrganisation(organisationId);

        Map<String, BigDecimal[]> totauxParCompte = calculerTotauxParCompte(organisationId, dateDebut, dateFin);

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Document doc = new Document(PageSize.A4.rotate(), 20, 20, 40, 40);
            PdfWriter.getInstance(doc, baos);
            doc.open();

            addTitrePage(doc, "BALANCE GÉNÉRALE", org.getNom(), dateDebut, dateFin);

            PdfPTable table = new PdfPTable(6);
            table.setWidthPercentage(100);
            table.setWidths(new float[]{10f, 30f, 8f, 15f, 15f, 15f});

            addHeaderCell(table, "Compte");
            addHeaderCell(table, "Libellé");
            addHeaderCell(table, "Classe");
            addHeaderCell(table, "Cumul Débit");
            addHeaderCell(table, "Cumul Crédit");
            addHeaderCell(table, "Solde");

            BigDecimal totalDebit = BigDecimal.ZERO;
            BigDecimal totalCredit = BigDecimal.ZERO;
            boolean alt = false;

            for (CompteComptable compte : comptes) {
                BigDecimal[] totaux = totauxParCompte.getOrDefault(
                        compte.getNumeroCompte(), new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
                BigDecimal debit = totaux[0];
                BigDecimal credit = totaux[1];
                BigDecimal solde = debit.subtract(credit);

                if (debit.signum() == 0 && credit.signum() == 0) continue;

                Color bg = alt ? COLOR_ROW_ALT : Color.WHITE;
                addDataCell(table, compte.getNumeroCompte(), bg);
                addDataCell(table, compte.getLibelle(), bg);
                addDataCell(table, String.valueOf(compte.getClasseComptable()), bg);
                addAmountCell(table, debit, bg);
                addAmountCell(table, credit, bg);
                addAmountCell(table, solde, bg);

                totalDebit = totalDebit.add(debit);
                totalCredit = totalCredit.add(credit);
                alt = !alt;
            }

            // Ligne totaux
            BigDecimal totalSolde = totalDebit.subtract(totalCredit);
            addTotalCell(table, "TOTAUX");
            addTotalCell(table, "");
            addTotalCell(table, "");
            addAmountCell(table, totalDebit, COLOR_TOTAL_ROW);
            addAmountCell(table, totalCredit, COLOR_TOTAL_ROW);
            addAmountCell(table, totalSolde, COLOR_TOTAL_ROW);

            doc.add(table);
            addFooter(doc);
            doc.close();
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Erreur génération balance PDF : {}", e.getMessage(), e);
            throw new RuntimeException("Erreur génération balance PDF", e);
        }
    }

    // ── Compte de résultat ────────────────────────────────────────────────────

    /**
     * Génère le compte de résultat SYSCOHADA.
     * Produits (classes 7 et 8 produits) — Charges (classes 6 et 8 charges).
     */
    public byte[] genererCompteResultat(UUID organisationId, LocalDate dateDebut, LocalDate dateFin) {
        Organisation org = getOrg(organisationId);
        Map<String, BigDecimal[]> totaux = calculerTotauxParCompte(organisationId, dateDebut, dateFin);

        List<CompteComptable> comptes = compteComptableRepository.findByOrganisation(organisationId);

        BigDecimal totalProduits = BigDecimal.ZERO;
        BigDecimal totalCharges = BigDecimal.ZERO;
        List<Object[]> lignesProduits = new ArrayList<>();
        List<Object[]> lignesCharges = new ArrayList<>();

        for (CompteComptable compte : comptes) {
            int classe = compte.getClasseComptable();
            BigDecimal[] t = totaux.getOrDefault(compte.getNumeroCompte(),
                    new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
            BigDecimal solde = t[1].subtract(t[0]); // crédit - débit pour produits

            if ((classe == 7) || (classe == 8 && TypeCompteComptable.PRODUITS.equals(compte.getTypeCompte()))) {
                if (solde.signum() != 0) {
                    lignesProduits.add(new Object[]{compte.getNumeroCompte(), compte.getLibelle(), solde});
                    totalProduits = totalProduits.add(solde);
                }
            } else if ((classe == 6) || (classe == 8 && TypeCompteComptable.CHARGES.equals(compte.getTypeCompte()))) {
                BigDecimal soldeCharge = t[0].subtract(t[1]); // débit - crédit pour charges
                if (soldeCharge.signum() != 0) {
                    lignesCharges.add(new Object[]{compte.getNumeroCompte(), compte.getLibelle(), soldeCharge});
                    totalCharges = totalCharges.add(soldeCharge);
                }
            }
        }

        BigDecimal resultat = totalProduits.subtract(totalCharges);
        boolean benefice = resultat.signum() >= 0;

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Document doc = new Document(PageSize.A4, 30, 30, 50, 40);
            PdfWriter.getInstance(doc, baos);
            doc.open();

            addTitrePage(doc, "COMPTE DE RÉSULTAT", org.getNom(), dateDebut, dateFin);

            // Section PRODUITS
            addSectionTitle(doc, "PRODUITS D'EXPLOITATION");
            PdfPTable tableProduits = creerTableau2Colonnes();
            for (Object[] ligne : lignesProduits) {
                addDataCell(tableProduits, ligne[0] + " — " + ligne[1], Color.WHITE);
                addAmountCell(tableProduits, (BigDecimal) ligne[2], Color.WHITE);
            }
            addTotalCell(tableProduits, "TOTAL PRODUITS");
            addAmountCell(tableProduits, totalProduits, COLOR_TOTAL_ROW);
            doc.add(tableProduits);

            doc.add(new Paragraph(" "));

            // Section CHARGES
            addSectionTitle(doc, "CHARGES D'EXPLOITATION");
            PdfPTable tableCharges = creerTableau2Colonnes();
            for (Object[] ligne : lignesCharges) {
                addDataCell(tableCharges, ligne[0] + " — " + ligne[1], Color.WHITE);
                addAmountCell(tableCharges, (BigDecimal) ligne[2], Color.WHITE);
            }
            addTotalCell(tableCharges, "TOTAL CHARGES");
            addAmountCell(tableCharges, totalCharges, COLOR_TOTAL_ROW);
            doc.add(tableCharges);

            doc.add(new Paragraph(" "));

            // Résultat net
            PdfPTable tableResultat = creerTableau2Colonnes();
            String libelleResultat = benefice ? "BÉNÉFICE NET DE L'EXERCICE" : "PERTE NETTE DE L'EXERCICE";
            Color couleurResultat = benefice ? new Color(0x00, 0x80, 0x00) : new Color(0xCC, 0x00, 0x00);
            PdfPCell cellResultat = new PdfPCell(
                    new Phrase(libelleResultat, FontFactory.getFont(FontFactory.HELVETICA_BOLD, 11, couleurResultat)));
            cellResultat.setBackgroundColor(new Color(0xF0, 0xF8, 0xE8));
            cellResultat.setPadding(8);
            tableResultat.addCell(cellResultat);
            addAmountCell(tableResultat, resultat.abs(), new Color(0xF0, 0xF8, 0xE8));
            doc.add(tableResultat);

            addFooter(doc);
            doc.close();
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Erreur génération compte de résultat PDF : {}", e.getMessage(), e);
            throw new RuntimeException("Erreur génération compte de résultat PDF", e);
        }
    }

    // ── Grand livre ───────────────────────────────────────────────────────────

    /**
     * Génère le grand livre pour un compte donné.
     */
    public byte[] genererGrandLivre(UUID organisationId, String numeroCompte,
                                     LocalDate dateDebut, LocalDate dateFin) {
        Organisation org = getOrg(organisationId);
        CompteComptable compte = compteComptableRepository
                .findByOrganisationAndNumero(organisationId, numeroCompte)
                .orElseThrow(() -> new NotFoundException(
                        "Compte " + numeroCompte + " introuvable pour l'org " + organisationId));

        List<EcritureComptable> ecritures = ecritureComptableRepository
                .findByOrganisationAndDateRange(organisationId, dateDebut, dateFin);

        // Filtrer les lignes qui concernent ce compte
        List<Object[]> mouvements = new ArrayList<>();
        BigDecimal solde = BigDecimal.ZERO;

        for (EcritureComptable ecriture : ecritures) {
            if (ecriture.getLignes() == null) continue;
            for (LigneEcriture ligne : ecriture.getLignes()) {
                if (ligne.getCompteComptable() == null) continue;
                if (!numeroCompte.equals(ligne.getCompteComptable().getNumeroCompte())) continue;

                BigDecimal debit = ligne.getMontantDebit() != null ? ligne.getMontantDebit() : BigDecimal.ZERO;
                BigDecimal credit = ligne.getMontantCredit() != null ? ligne.getMontantCredit() : BigDecimal.ZERO;
                solde = solde.add(debit).subtract(credit);

                mouvements.add(new Object[]{
                        ecriture.getDateEcriture(),
                        ecriture.getNumeroPiece(),
                        ecriture.getLibelle(),
                        debit,
                        credit,
                        solde
                });
            }
        }

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Document doc = new Document(PageSize.A4.rotate(), 20, 20, 40, 40);
            PdfWriter.getInstance(doc, baos);
            doc.open();

            addTitrePage(doc, "GRAND LIVRE — " + numeroCompte + " " + compte.getLibelle(),
                    org.getNom(), dateDebut, dateFin);

            PdfPTable table = new PdfPTable(6);
            table.setWidthPercentage(100);
            table.setWidths(new float[]{12f, 15f, 35f, 12f, 12f, 14f});

            addHeaderCell(table, "Date");
            addHeaderCell(table, "Pièce");
            addHeaderCell(table, "Libellé");
            addHeaderCell(table, "Débit");
            addHeaderCell(table, "Crédit");
            addHeaderCell(table, "Solde cumulé");

            boolean alt = false;
            for (Object[] mvt : mouvements) {
                Color bg = alt ? COLOR_ROW_ALT : Color.WHITE;
                addDataCell(table, DATE_FMT.format((LocalDate) mvt[0]), bg);
                addDataCell(table, (String) mvt[1], bg);
                addDataCell(table, (String) mvt[2], bg);
                addAmountCell(table, (BigDecimal) mvt[3], bg);
                addAmountCell(table, (BigDecimal) mvt[4], bg);
                addAmountCell(table, (BigDecimal) mvt[5], bg);
                alt = !alt;
            }

            if (mouvements.isEmpty()) {
                PdfPCell empty = new PdfPCell(new Phrase("Aucun mouvement sur la période",
                        FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 10, Color.GRAY)));
                empty.setColspan(6);
                empty.setPadding(10);
                empty.setHorizontalAlignment(Element.ALIGN_CENTER);
                table.addCell(empty);
            }

            doc.add(table);
            addFooter(doc);
            doc.close();
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Erreur génération grand livre PDF : {}", e.getMessage(), e);
            throw new RuntimeException("Erreur génération grand livre PDF", e);
        }
    }

    // ── Utilitaires PDF ──────────────────────────────────────────────────────

    private void addTitrePage(Document doc, String titre, String orgNom,
                               LocalDate dateDebut, LocalDate dateFin) throws DocumentException {
        Font fontTitre = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16, COLOR_HEADER);
        Font fontSousTitre = FontFactory.getFont(FontFactory.HELVETICA, 11, Color.DARK_GRAY);

        Paragraph pTitre = new Paragraph(titre, fontTitre);
        pTitre.setAlignment(Element.ALIGN_CENTER);
        pTitre.setSpacingAfter(4);
        doc.add(pTitre);

        Paragraph pOrg = new Paragraph(orgNom, fontSousTitre);
        pOrg.setAlignment(Element.ALIGN_CENTER);
        doc.add(pOrg);

        if (dateDebut != null && dateFin != null) {
            Paragraph pPeriode = new Paragraph(
                    "Période : " + DATE_FMT.format(dateDebut) + " au " + DATE_FMT.format(dateFin),
                    FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 10, Color.GRAY));
            pPeriode.setAlignment(Element.ALIGN_CENTER);
            pPeriode.setSpacingAfter(12);
            doc.add(pPeriode);
        }
    }

    private void addSectionTitle(Document doc, String titre) throws DocumentException {
        Paragraph p = new Paragraph(titre,
                FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12, COLOR_HEADER));
        p.setSpacingBefore(8);
        p.setSpacingAfter(4);
        doc.add(p);
    }

    private void addFooter(Document doc) throws DocumentException {
        Paragraph footer = new Paragraph(
                "Généré le " + DATE_FMT.format(LocalDate.now()) + " — UnionFlow SYSCOHADA révisé",
                FontFactory.getFont(FontFactory.HELVETICA_OBLIQUE, 8, Color.GRAY));
        footer.setAlignment(Element.ALIGN_RIGHT);
        footer.setSpacingBefore(16);
        doc.add(footer);
    }

    private PdfPTable creerTableau2Colonnes() throws DocumentException {
        PdfPTable table = new PdfPTable(2);
        table.setWidthPercentage(100);
        table.setWidths(new float[]{65f, 35f});
        return table;
    }

    private void addHeaderCell(PdfPTable table, String text) {
        PdfPCell cell = new PdfPCell(new Phrase(text,
                FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9, COLOR_HEADER_TEXT)));
        cell.setBackgroundColor(COLOR_HEADER);
        cell.setPadding(6);
        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        table.addCell(cell);
    }

    private void addDataCell(PdfPTable table, String text, Color bg) {
        PdfPCell cell = new PdfPCell(new Phrase(text,
                FontFactory.getFont(FontFactory.HELVETICA, 9, Color.BLACK)));
        cell.setBackgroundColor(bg);
        cell.setPadding(5);
        table.addCell(cell);
    }

    private void addAmountCell(PdfPTable table, BigDecimal amount, Color bg) {
        String formatted = amount != null
                ? String.format("%,.0f XOF", amount.doubleValue())
                : "0 XOF";
        PdfPCell cell = new PdfPCell(new Phrase(formatted,
                FontFactory.getFont(FontFactory.HELVETICA, 9, Color.BLACK)));
        cell.setBackgroundColor(bg);
        cell.setPadding(5);
        cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
        table.addCell(cell);
    }

    private void addTotalCell(PdfPTable table, String text) {
        PdfPCell cell = new PdfPCell(new Phrase(text,
                FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9, Color.BLACK)));
        cell.setBackgroundColor(COLOR_TOTAL_ROW);
        cell.setPadding(6);
        table.addCell(cell);
    }

    // ── Calcul des totaux ─────────────────────────────────────────────────────

    private Map<String, BigDecimal[]> calculerTotauxParCompte(UUID organisationId,
                                                               LocalDate dateDebut, LocalDate dateFin) {
        List<EcritureComptable> ecritures = ecritureComptableRepository
                .findByOrganisationAndDateRange(organisationId, dateDebut, dateFin);

        Map<String, BigDecimal[]> totaux = new HashMap<>();
        for (EcritureComptable ecriture : ecritures) {
            if (ecriture.getLignes() == null) continue;
            for (LigneEcriture ligne : ecriture.getLignes()) {
                if (ligne.getCompteComptable() == null) continue;
                String numero = ligne.getCompteComptable().getNumeroCompte();
                totaux.computeIfAbsent(numero, k -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO});
                BigDecimal debit = ligne.getMontantDebit() != null ? ligne.getMontantDebit() : BigDecimal.ZERO;
                BigDecimal credit = ligne.getMontantCredit() != null ? ligne.getMontantCredit() : BigDecimal.ZERO;
                totaux.get(numero)[0] = totaux.get(numero)[0].add(debit);
                totaux.get(numero)[1] = totaux.get(numero)[1].add(credit);
            }
        }
        return totaux;
    }

    private Organisation getOrg(UUID organisationId) {
        return organisationRepository.findByIdOptional(organisationId)
                .orElseThrow(() -> new NotFoundException("Organisation introuvable : " + organisationId));
    }
}
