package dev.lions.unionflow.server.service.mutuelle;

import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.*;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
import dev.lions.unionflow.server.entity.mutuelle.parts.ComptePartsSociales;
import dev.lions.unionflow.server.entity.mutuelle.parts.TransactionPartsSociales;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.TransactionEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.ComptePartsSocialesRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.TransactionPartsSocialesRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;

import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Génère les relevés de compte en PDF (OpenPDF).
 * Deux types : relevé épargne, relevé parts sociales.
 */
@ApplicationScoped
public class ReleveComptePdfService {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
    private static final Color BLEU_UNIONFLOW = new Color(30, 90, 160);
    private static final Color GRIS_ENTETE = new Color(240, 240, 245);

    @Inject CompteEpargneRepository compteEpargneRepository;
    @Inject TransactionEpargneRepository transactionEpargneRepository;
    @Inject ComptePartsSocialesRepository comptePartsSocialesRepository;
    @Inject TransactionPartsSocialesRepository transactionPartsSocialesRepository;

    // ─── Relevé Épargne ────────────────────────────────────────────────────────

    public byte[] genererReleveEpargne(UUID compteId, LocalDate dateDebut, LocalDate dateFin) {
        CompteEpargne compte = compteEpargneRepository.findByIdOptional(compteId)
                .orElseThrow(() -> new NotFoundException("Compte épargne introuvable: " + compteId));

        List<TransactionEpargne> txs = transactionEpargneRepository
                .find("compte.id = ?1 ORDER BY dateTransaction ASC", compteId)
                .list();

        if (dateDebut != null) {
            txs = txs.stream()
                    .filter(t -> !t.getDateTransaction().toLocalDate().isBefore(dateDebut))
                    .collect(Collectors.toList());
        }
        if (dateFin != null) {
            txs = txs.stream()
                    .filter(t -> !t.getDateTransaction().toLocalDate().isAfter(dateFin))
                    .collect(Collectors.toList());
        }

        return buildPdfEpargne(compte, txs, dateDebut, dateFin);
    }

    // ─── Relevé Parts Sociales ─────────────────────────────────────────────────

    public byte[] genererReleveParts(UUID compteId, LocalDate dateDebut, LocalDate dateFin) {
        ComptePartsSociales compte = comptePartsSocialesRepository.findByIdOptional(compteId)
                .orElseThrow(() -> new NotFoundException("Compte parts sociales introuvable: " + compteId));

        List<TransactionPartsSociales> txs = transactionPartsSocialesRepository.findByCompte(compteId);
        // findByCompte is DESC — reverse for statement order
        java.util.Collections.reverse(txs);

        if (dateDebut != null) {
            txs = txs.stream()
                    .filter(t -> !t.getDateTransaction().toLocalDate().isBefore(dateDebut))
                    .collect(Collectors.toList());
        }
        if (dateFin != null) {
            txs = txs.stream()
                    .filter(t -> !t.getDateTransaction().toLocalDate().isAfter(dateFin))
                    .collect(Collectors.toList());
        }

        return buildPdfParts(compte, txs, dateDebut, dateFin);
    }

    // ─── PDF builders ──────────────────────────────────────────────────────────

    private byte[] buildPdfEpargne(CompteEpargne compte, List<TransactionEpargne> txs,
                                    LocalDate dateDebut, LocalDate dateFin) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Document doc = new Document(PageSize.A4, 40, 40, 60, 40);
            PdfWriter.getInstance(doc, baos);
            doc.open();

            addHeader(doc, compte.getOrganisation() != null ? compte.getOrganisation().getNom() : "UnionFlow",
                    "RELEVÉ DE COMPTE ÉPARGNE");
            addInfoBlock(doc, new String[][]{
                    {"Numéro de compte", compte.getNumeroCompte()},
                    {"Type de compte", compte.getTypeCompte() != null ? compte.getTypeCompte().name() : ""},
                    {"Titulaire", membreNom(compte)},
                    {"Période", formatPeriode(dateDebut, dateFin)},
                    {"Date d'édition", LocalDate.now().format(DATE_FMT)},
                    {"Solde actuel", formatMontant(compte.getSoldeActuel())}
            });

            // Solde d'ouverture de la période
            BigDecimal soldeOuverture = txs.isEmpty() ? compte.getSoldeActuel()
                    : txs.get(0).getSoldeAvant();

            PdfPTable table = createTable(new float[]{2f, 3f, 2.5f, 2.5f, 2.5f},
                    new String[]{"Date", "Libellé", "Débit", "Crédit", "Solde"});

            addLigneTotal(table, "Solde d'ouverture", null, null, soldeOuverture);

            for (TransactionEpargne tx : txs) {
                boolean isDebit = isDebitEpargne(tx);
                table.addCell(cell(tx.getDateTransaction().format(DATE_FMT), false));
                table.addCell(cell(tx.getMotif() != null ? tx.getMotif() : tx.getType().name(), false));
                table.addCell(cellAmount(isDebit ? tx.getMontant() : null, true));
                table.addCell(cellAmount(!isDebit ? tx.getMontant() : null, false));
                table.addCell(cellAmount(tx.getSoldeApres(), false));
            }

            doc.add(table);
            addSoldeFinal(doc, compte.getSoldeActuel());
            addFooter(doc);
            doc.close();
            return baos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Erreur génération relevé épargne PDF: " + e.getMessage(), e);
        }
    }

    private byte[] buildPdfParts(ComptePartsSociales compte, List<TransactionPartsSociales> txs,
                                  LocalDate dateDebut, LocalDate dateFin) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Document doc = new Document(PageSize.A4, 40, 40, 60, 40);
            PdfWriter.getInstance(doc, baos);
            doc.open();

            addHeader(doc, compte.getOrganisation() != null ? compte.getOrganisation().getNom() : "UnionFlow",
                    "RELEVÉ DE PARTS SOCIALES");
            addInfoBlock(doc, new String[][]{
                    {"Numéro de compte", compte.getNumeroCompte()},
                    {"Titulaire", compte.getMembre() != null
                            ? compte.getMembre().getNom() + " " + compte.getMembre().getPrenom() : ""},
                    {"Valeur nominale", formatMontant(compte.getValeurNominale()) + " / part"},
                    {"Parts détenues", String.valueOf(compte.getNombreParts())},
                    {"Capital total", formatMontant(compte.getMontantTotal())},
                    {"Dividendes reçus", formatMontant(compte.getTotalDividendesRecus())},
                    {"Période", formatPeriode(dateDebut, dateFin)},
                    {"Date d'édition", LocalDate.now().format(DATE_FMT)}
            });

            PdfPTable table = createTable(new float[]{2f, 3f, 2f, 2.5f, 2.5f, 2.5f},
                    new String[]{"Date", "Libellé", "Parts", "Montant", "Avant", "Après"});

            for (TransactionPartsSociales tx : txs) {
                table.addCell(cell(tx.getDateTransaction().format(DATE_FMT), false));
                table.addCell(cell(tx.getMotif() != null ? tx.getMotif() : tx.getTypeTransaction().getLibelle(), false));
                table.addCell(cell(String.valueOf(tx.getNombreParts()), false));
                table.addCell(cellAmount(tx.getMontant(), false));
                table.addCell(cell(String.valueOf(tx.getSoldePartsAvant()), false));
                table.addCell(cell(String.valueOf(tx.getSoldePartsApres()), false));
            }

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

    // ─── PDF helpers ───────────────────────────────────────────────────────────

    private void addHeader(Document doc, String orgNom, String titre) throws DocumentException {
        Font fontOrg = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16, BLEU_UNIONFLOW);
        Font fontTitre = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 13);
        Font fontSub = FontFactory.getFont(FontFactory.HELVETICA, 9, Color.GRAY);

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

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

        Paragraph pSub = new Paragraph("Édité via UnionFlow · " + LocalDateTime.now().format(DATETIME_FMT), fontSub);
        pSub.setAlignment(Element.ALIGN_CENTER);
        pSub.setSpacingAfter(16);
        doc.add(pSub);

        // Separator line
        PdfPTable sep = new PdfPTable(1);
        sep.setWidthPercentage(100);
        PdfPCell line = new PdfPCell(new Phrase(" "));
        line.setBorderColor(BLEU_UNIONFLOW);
        line.setBorderWidthBottom(1.5f);
        line.setBorderWidthTop(0);
        line.setBorderWidthLeft(0);
        line.setBorderWidthRight(0);
        line.setPaddingBottom(4);
        sep.addCell(line);
        doc.add(sep);
        doc.add(Chunk.NEWLINE);
    }

    private void addInfoBlock(Document doc, String[][] lignes) throws DocumentException {
        PdfPTable t = new PdfPTable(2);
        t.setWidthPercentage(60);
        t.setHorizontalAlignment(Element.ALIGN_LEFT);
        t.setWidths(new float[]{2f, 3f});
        Font fontLabel = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9);
        Font fontVal = FontFactory.getFont(FontFactory.HELVETICA, 9);
        for (String[] row : lignes) {
            PdfPCell cLabel = new PdfPCell(new Phrase(row[0], fontLabel));
            cLabel.setBorder(Rectangle.NO_BORDER);
            cLabel.setPadding(3);
            PdfPCell cVal = new PdfPCell(new Phrase(row[1], fontVal));
            cVal.setBorder(Rectangle.NO_BORDER);
            cVal.setPadding(3);
            t.addCell(cLabel);
            t.addCell(cVal);
        }
        t.setSpacingAfter(12);
        doc.add(t);
    }

    private PdfPTable createTable(float[] widths, String[] headers) throws DocumentException {
        PdfPTable table = new PdfPTable(widths.length);
        table.setWidthPercentage(100);
        table.setWidths(widths);
        Font fontHeader = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 9, Color.WHITE);
        for (String h : headers) {
            PdfPCell cell = new PdfPCell(new Phrase(h, fontHeader));
            cell.setBackgroundColor(BLEU_UNIONFLOW);
            cell.setPadding(5);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(cell);
        }
        table.setHeaderRows(1);
        return table;
    }

    private PdfPCell cell(String text, boolean bold) {
        Font f = bold
                ? FontFactory.getFont(FontFactory.HELVETICA_BOLD, 8)
                : FontFactory.getFont(FontFactory.HELVETICA, 8);
        PdfPCell c = new PdfPCell(new Phrase(text != null ? text : "", f));
        c.setPadding(4);
        c.setBorderColor(Color.LIGHT_GRAY);
        return c;
    }

    private PdfPCell cellAmount(BigDecimal amount, boolean isDebit) {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) {
            PdfPCell c = cell("", false);
            c.setHorizontalAlignment(Element.ALIGN_RIGHT);
            return c;
        }
        Font f = FontFactory.getFont(FontFactory.HELVETICA, 8,
                isDebit ? new Color(180, 0, 0) : new Color(0, 130, 0));
        PdfPCell c = new PdfPCell(new Phrase(formatMontant(amount), f));
        c.setPadding(4);
        c.setHorizontalAlignment(Element.ALIGN_RIGHT);
        c.setBorderColor(Color.LIGHT_GRAY);
        return c;
    }

    private void addLigneTotal(PdfPTable table, String label, BigDecimal debit,
                                BigDecimal credit, BigDecimal solde) {
        Font f = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 8);
        PdfPCell cLabel = new PdfPCell(new Phrase(label, f));
        cLabel.setColspan(2);
        cLabel.setBackgroundColor(GRIS_ENTETE);
        cLabel.setPadding(4);
        table.addCell(cLabel);
        table.addCell(cellAmount(debit, true));
        table.addCell(cellAmount(credit, false));
        PdfPCell cSolde = cellAmount(solde, false);
        cSolde.setBackgroundColor(GRIS_ENTETE);
        table.addCell(cSolde);
    }

    private void addSoldeFinal(Document doc, BigDecimal solde) throws DocumentException {
        Font f = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 10, BLEU_UNIONFLOW);
        Paragraph p = new Paragraph("Solde final : " + formatMontant(solde), f);
        p.setAlignment(Element.ALIGN_RIGHT);
        p.setSpacingBefore(8);
        doc.add(p);
    }

    private void addFooter(Document doc) throws DocumentException {
        Font f = FontFactory.getFont(FontFactory.HELVETICA, 8, Color.GRAY);
        Paragraph p = new Paragraph(
                "Document généré automatiquement par UnionFlow — confidentiel.", f);
        p.setAlignment(Element.ALIGN_CENTER);
        p.setSpacingBefore(20);
        doc.add(p);
    }

    private String formatMontant(BigDecimal m) {
        if (m == null) return "0 XOF";
        return String.format("%,.0f XOF", m.doubleValue());
    }

    private String formatPeriode(LocalDate debut, LocalDate fin) {
        if (debut == null && fin == null) return "Toutes opérations";
        if (debut == null) return "jusqu'au " + fin.format(DATE_FMT);
        if (fin == null) return "depuis le " + debut.format(DATE_FMT);
        return debut.format(DATE_FMT) + " au " + fin.format(DATE_FMT);
    }

    private String membreNom(CompteEpargne compte) {
        if (compte.getMembre() == null) return "";
        return compte.getMembre().getNom() + " " + compte.getMembre().getPrenom();
    }

    private boolean isDebitEpargne(TransactionEpargne tx) {
        return switch (tx.getType()) {
            case RETRAIT, PRELEVEMENT_FRAIS, TRANSFERT_SORTANT, REMBOURSEMENT_CREDIT, RETENUE_GARANTIE -> true;
            default -> false;
        };
    }
}
