"""
DOOBAT - Service PDF
Génération de PDF professionnels avec ReportLab.
Copyright 2026 Vincent Gallet - Vincesoft
"""

import logging
import os
import re
import uuid
from configparser import ConfigParser
from datetime import datetime
from typing import Optional

from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.platypus import (
    HRFlowable,
    Image,
    PageBreak,
    Paragraph,
    SimpleDocTemplate,
    Spacer,
    Table,
    TableStyle,
)

logger = logging.getLogger("doobat.pdf")

# ── Palette de couleurs DOOBAT ─────────────────────────────────────────────────

COLOR_PRIMARY = colors.HexColor("#1A73E8")     # Bleu principal
COLOR_SECONDARY = colors.HexColor("#34A853")   # Vert
COLOR_DARK = colors.HexColor("#202124")        # Noir doux
COLOR_LIGHT_GREY = colors.HexColor("#F8F9FA")  # Fond lignes paires
COLOR_BORDER = colors.HexColor("#DADCE0")      # Bordures
COLOR_WHITE = colors.white
COLOR_RED = colors.HexColor("#EA4335")


# ── Helpers sécurité ───────────────────────────────────────────────────────────

def sanitize_filename(name: str) -> str:
    """Nettoie un nom de fichier pour éviter path traversal et caractères illégaux."""
    name = re.sub(r"[^\w\-_\.]", "_", name)
    name = re.sub(r"\.{2,}", ".", name)  # Anti path traversal
    return name[:80]


def safe_text(value) -> str:
    """Convertit une valeur en texte sûr pour ReportLab."""
    if value is None or value is False:
        return ""
    text = str(value)
    # Échapper les caractères XML spéciaux
    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    return text[:500]


def format_currency(amount, currency: str = "EUR") -> str:
    """Formate un montant monétaire."""
    try:
        return f"{float(amount):,.2f} {currency}".replace(",", " ")
    except (TypeError, ValueError):
        return "0,00 EUR"


def format_date(date_str: str) -> str:
    """Convertit une date Odoo (YYYY-MM-DD) en format français."""
    if not date_str:
        return ""
    try:
        d = datetime.strptime(str(date_str)[:10], "%Y-%m-%d")
        return d.strftime("%d/%m/%Y")
    except ValueError:
        return str(date_str)


# ── Service PDF ────────────────────────────────────────────────────────────────

class PDFService:
    """
    Génère des PDF professionnels pour :
    - Tarif complet (catalogue articles Odoo)
    - Stock article
    - Documents client (devis, commandes, factures)
    """

    FOOTER_TEXT = "Copyright 2026 Vincent Gallet - Vincesoft | DOOBAT Bot Telegram"

    def __init__(self, config: ConfigParser):
        self._output_dir = config.get("PDF", "output_dir", fallback="pdf")
        self._logo_path = config.get("PDF", "logo_path", fallback="static/logo.png")
        os.makedirs(self._output_dir, exist_ok=True)
        self._styles = self._build_styles()
        logger.info(f"PDFService initialisé, output={self._output_dir}")

    def _build_styles(self) -> dict:
        """Construit les styles ReportLab réutilisables."""
        base = getSampleStyleSheet()
        styles = {}

        styles["title"] = ParagraphStyle(
            "Title",
            fontSize=20,
            fontName="Helvetica-Bold",
            textColor=COLOR_PRIMARY,
            alignment=TA_LEFT,
            spaceAfter=6,
        )
        styles["subtitle"] = ParagraphStyle(
            "Subtitle",
            fontSize=12,
            fontName="Helvetica",
            textColor=COLOR_DARK,
            alignment=TA_LEFT,
            spaceAfter=4,
        )
        styles["section"] = ParagraphStyle(
            "Section",
            fontSize=11,
            fontName="Helvetica-Bold",
            textColor=COLOR_PRIMARY,
            spaceBefore=10,
            spaceAfter=4,
        )
        styles["normal"] = ParagraphStyle(
            "Normal",
            fontSize=9,
            fontName="Helvetica",
            textColor=COLOR_DARK,
        )
        styles["footer"] = ParagraphStyle(
            "Footer",
            fontSize=7,
            fontName="Helvetica",
            textColor=colors.grey,
            alignment=TA_CENTER,
        )
        styles["cell"] = ParagraphStyle(
            "Cell",
            fontSize=8,
            fontName="Helvetica",
            textColor=COLOR_DARK,
        )
        styles["cell_bold"] = ParagraphStyle(
            "CellBold",
            fontSize=8,
            fontName="Helvetica-Bold",
            textColor=COLOR_DARK,
        )
        return styles

    # ── En-tête commun ─────────────────────────────────────────────────────────

    def _build_header(self, title: str, subtitle: str = "", requester: str = "") -> list:
        """Construit l'en-tête standard DOOBAT pour tous les PDF."""
        elements = []

        # Logo + titre côte à côte
        header_data = []
        if os.path.exists(self._logo_path):
            try:
                logo = Image(self._logo_path, width=3 * cm, height=1.5 * cm)
                logo.hAlign = "LEFT"
                header_data.append(logo)
            except Exception:
                header_data.append(Paragraph("DOOBAT", self._styles["title"]))
        else:
            header_data.append(Paragraph("DOOBAT", self._styles["title"]))

        title_block = [
            Paragraph(safe_text(title), self._styles["title"]),
        ]
        if subtitle:
            title_block.append(Paragraph(safe_text(subtitle), self._styles["subtitle"]))

        date_str = datetime.now().strftime("%d/%m/%Y %H:%M")
        info_lines = [f"Généré le : {date_str}"]
        if requester:
            info_lines.append(f"Demandé par : {safe_text(requester)}")
        info_block = "<br/>".join(info_lines)

        t = Table(
            [[header_data[0], title_block, Paragraph(info_block, self._styles["normal"])]],
            colWidths=[3.5 * cm, 10 * cm, 5.5 * cm],
        )
        t.setStyle(TableStyle([
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ("ALIGN", (2, 0), (2, 0), "RIGHT"),
        ]))
        elements.append(t)
        elements.append(HRFlowable(width="100%", thickness=2, color=COLOR_PRIMARY))
        elements.append(Spacer(1, 0.3 * cm))
        return elements

    def _build_footer_callback(self):
        """Callback pour numérotation de page en pied de page."""
        footer_text = self.FOOTER_TEXT

        def footer(canvas, doc):
            canvas.saveState()
            canvas.setFont("Helvetica", 7)
            canvas.setFillColor(colors.grey)
            # Ligne de séparation
            canvas.setStrokeColor(COLOR_BORDER)
            canvas.line(
                doc.leftMargin,
                doc.bottomMargin - 5,
                doc.width + doc.leftMargin,
                doc.bottomMargin - 5,
            )
            # Texte gauche
            canvas.drawString(doc.leftMargin, doc.bottomMargin - 15, footer_text)
            # Numéro de page droite
            page_text = f"Page {doc.page}"
            canvas.drawRightString(
                doc.width + doc.leftMargin, doc.bottomMargin - 15, page_text
            )
            canvas.restoreState()

        return footer

    def _make_filepath(self, prefix: str, identifier: str = "") -> str:
        """Génère un chemin de fichier PDF unique et sécurisé."""
        clean_id = sanitize_filename(identifier) if identifier else ""
        unique = uuid.uuid4().hex[:8]
        filename = f"{prefix}_{clean_id}_{unique}.pdf" if clean_id else f"{prefix}_{unique}.pdf"
        return os.path.join(self._output_dir, filename)

    # ── Tarif ──────────────────────────────────────────────────────────────────

    def generate_tarif_pdf(
        self, products: list[dict], requester: str = ""
    ) -> Optional[str]:
        """
        Génère le PDF du catalogue tarifaire complet.
        Retourne le chemin du fichier généré ou None en cas d'erreur.
        """
        filepath = self._make_filepath("tarif")
        try:
            doc = SimpleDocTemplate(
                filepath,
                pagesize=A4,
                leftMargin=1.5 * cm,
                rightMargin=1.5 * cm,
                topMargin=2 * cm,
                bottomMargin=2 * cm,
            )

            elements = self._build_header(
                title="Catalogue Tarifaire",
                subtitle=f"{len(products)} article(s) actif(s)",
                requester=requester,
            )

            # Regrouper par catégorie
            categories: dict[str, list] = {}
            for p in products:
                cat = p.get("categ_id", [None, "Sans catégorie"])
                cat_name = cat[1] if isinstance(cat, (list, tuple)) and len(cat) > 1 else "Sans catégorie"
                categories.setdefault(str(cat_name), []).append(p)

            for cat_name in sorted(categories.keys()):
                cat_products = categories[cat_name]
                elements.append(Paragraph(safe_text(cat_name), self._styles["section"]))

                # En-tête tableau
                header_row = [
                    Paragraph("<b>Référence</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Désignation</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Type</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Prix HT</b>", self._styles["cell_bold"]),
                ]

                rows = [header_row]
                for i, p in enumerate(cat_products):
                    ref = safe_text(p.get("default_code") or "")
                    name = safe_text(p.get("name", ""))
                    ptype = safe_text(p.get("type", ""))
                    price = format_currency(p.get("list_price", 0))

                    bg = COLOR_LIGHT_GREY if i % 2 == 0 else COLOR_WHITE
                    rows.append([
                        Paragraph(ref, self._styles["cell"]),
                        Paragraph(name, self._styles["cell"]),
                        Paragraph(ptype, self._styles["cell"]),
                        Paragraph(price, self._styles["cell"]),
                    ])

                col_widths = [3 * cm, 9 * cm, 2.5 * cm, 3.5 * cm]
                t = Table(rows, colWidths=col_widths, repeatRows=1)
                t.setStyle(self._table_style_tarif(len(rows)))
                elements.append(t)
                elements.append(Spacer(1, 0.4 * cm))

            footer_cb = self._build_footer_callback()
            doc.build(elements, onFirstPage=footer_cb, onLaterPages=footer_cb)
            logger.info(f"PDF tarif généré : {filepath}")
            return filepath

        except Exception as e:
            logger.error(f"Erreur génération PDF tarif : {e}", exc_info=True)
            return None

    @staticmethod
    def _table_style_tarif(num_rows: int) -> TableStyle:
        """Style tableau pour le tarif."""
        style = [
            # En-tête
            ("BACKGROUND", (0, 0), (-1, 0), COLOR_PRIMARY),
            ("TEXTCOLOR", (0, 0), (-1, 0), COLOR_WHITE),
            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, 0), 9),
            ("BOTTOMPADDING", (0, 0), (-1, 0), 6),
            ("TOPPADDING", (0, 0), (-1, 0), 6),
            # Lignes données
            ("FONTSIZE", (0, 1), (-1, -1), 8),
            ("TOPPADDING", (0, 1), (-1, -1), 4),
            ("BOTTOMPADDING", (0, 1), (-1, -1), 4),
            # Grille
            ("GRID", (0, 0), (-1, -1), 0.3, COLOR_BORDER),
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ("ALIGN", (3, 0), (3, -1), "RIGHT"),
        ]
        # Alternance de couleurs
        for i in range(1, num_rows):
            if i % 2 == 0:
                style.append(("BACKGROUND", (0, i), (-1, i), COLOR_LIGHT_GREY))
        return TableStyle(style)

    # ── Stock ──────────────────────────────────────────────────────────────────

    def generate_stock_pdf(
        self, products: list[dict], article_name: str = "", requester: str = ""
    ) -> Optional[str]:
        """
        Génère un PDF de stock pour un ou plusieurs articles.
        """
        filepath = self._make_filepath("stock", article_name)
        try:
            doc = SimpleDocTemplate(
                filepath,
                pagesize=A4,
                leftMargin=1.5 * cm,
                rightMargin=1.5 * cm,
                topMargin=2 * cm,
                bottomMargin=2 * cm,
            )

            elements = self._build_header(
                title="État du Stock",
                subtitle=f"Recherche : {safe_text(article_name)}" if article_name else "Stock général",
                requester=requester,
            )

            if not products:
                elements.append(
                    Paragraph(
                        f"Aucun article trouvé pour : {safe_text(article_name)}",
                        self._styles["normal"],
                    )
                )
            else:
                header_row = [
                    Paragraph("<b>Référence</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Désignation</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Catégorie</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Stock dispo</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Stock virtuel</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Prix HT</b>", self._styles["cell_bold"]),
                ]
                rows = [header_row]

                for i, p in enumerate(products):
                    ref = safe_text(p.get("default_code") or "")
                    name = safe_text(p.get("name", ""))
                    cat = p.get("categ_id", [None, ""])
                    cat_name = cat[1] if isinstance(cat, (list, tuple)) and len(cat) > 1 else ""
                    qty_avail = p.get("qty_available", 0)
                    qty_virtual = p.get("virtual_available", 0)
                    price = format_currency(p.get("list_price", 0))

                    # Couleur stock selon disponibilité
                    rows.append([
                        Paragraph(ref, self._styles["cell"]),
                        Paragraph(name, self._styles["cell"]),
                        Paragraph(safe_text(cat_name), self._styles["cell"]),
                        Paragraph(
                            f'<font color="{"red" if qty_avail <= 0 else "green"}">'
                            f'{qty_avail:.0f}</font>',
                            self._styles["cell"],
                        ),
                        Paragraph(f"{qty_virtual:.0f}", self._styles["cell"]),
                        Paragraph(price, self._styles["cell"]),
                    ])

                    # Détail par emplacement si disponible
                    details = p.get("stock_details", [])
                    for d in details:
                        loc = d.get("location_id", [None, ""])[1] if d.get("location_id") else ""
                        qty = d.get("quantity", 0)
                        reserved = d.get("reserved_quantity", 0)
                        rows.append([
                            Paragraph("", self._styles["cell"]),
                            Paragraph(
                                f"  → {safe_text(loc)}",
                                self._styles["cell"],
                            ),
                            Paragraph("", self._styles["cell"]),
                            Paragraph(f"{qty:.0f} (réservé: {reserved:.0f})", self._styles["cell"]),
                            Paragraph("", self._styles["cell"]),
                            Paragraph("", self._styles["cell"]),
                        ])

                col_widths = [2.5 * cm, 6 * cm, 3 * cm, 2.5 * cm, 2.5 * cm, 2.5 * cm]
                t = Table(rows, colWidths=col_widths, repeatRows=1)
                t.setStyle(self._table_style_stock(len(rows)))
                elements.append(t)

            footer_cb = self._build_footer_callback()
            doc.build(elements, onFirstPage=footer_cb, onLaterPages=footer_cb)
            logger.info(f"PDF stock généré : {filepath}")
            return filepath

        except Exception as e:
            logger.error(f"Erreur génération PDF stock : {e}", exc_info=True)
            return None

    @staticmethod
    def _table_style_stock(num_rows: int) -> TableStyle:
        """Style tableau pour le stock."""
        style = [
            ("BACKGROUND", (0, 0), (-1, 0), COLOR_SECONDARY),
            ("TEXTCOLOR", (0, 0), (-1, 0), COLOR_WHITE),
            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, 0), 9),
            ("BOTTOMPADDING", (0, 0), (-1, 0), 6),
            ("TOPPADDING", (0, 0), (-1, 0), 6),
            ("FONTSIZE", (0, 1), (-1, -1), 8),
            ("TOPPADDING", (0, 1), (-1, -1), 3),
            ("BOTTOMPADDING", (0, 1), (-1, -1), 3),
            ("GRID", (0, 0), (-1, -1), 0.3, COLOR_BORDER),
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ("ALIGN", (3, 0), (5, -1), "RIGHT"),
        ]
        for i in range(1, num_rows):
            if i % 2 == 0:
                style.append(("BACKGROUND", (0, i), (-1, i), COLOR_LIGHT_GREY))
        return TableStyle(style)

    # ── Documents client ───────────────────────────────────────────────────────

    def generate_client_documents_pdf(
        self,
        client_name: str,
        documents: dict,
        requester: str = "",
        months: int = 12,
        document_kind: str = "client",
    ) -> Optional[str]:
        """
        Génère un PDF complet des documents d'un client :
        devis, commandes, factures sur les N derniers mois.
        """
        is_supplier = document_kind == "supplier"
        entity_label = "Fournisseur" if is_supplier else "Client"
        filepath = self._make_filepath(f"{document_kind}_docs", client_name)
        try:
            doc = SimpleDocTemplate(
                filepath,
                pagesize=A4,
                leftMargin=1.5 * cm,
                rightMargin=1.5 * cm,
                topMargin=2 * cm,
                bottomMargin=2 * cm,
            )

            elements = self._build_header(
                title=f"Documents {entity_label} : {safe_text(client_name)}",
                subtitle=f"Historique {months} derniers mois",
                requester=requester,
            )

            state_map_orders = {
                "draft": "Devis", "sent": "Devis envoyé", "sale": "Confirmée",
                "done": "Verrouillée", "cancel": "Annulée",
            }
            state_map_invoices = {
                "draft": "Brouillon", "posted": "Publiée", "cancel": "Annulée",
            }
            payment_map = {
                "not_paid": "Non payée", "in_payment": "En cours",
                "paid": "Payée", "partial": "Partielle", "reversed": "Remboursée",
            }

            # ── Devis ──────────────────────────────────────────────────────────
            quotations = documents.get("quotations", [])
            if not is_supplier:
                elements.append(Paragraph(
                    f"Devis ({len(quotations)})", self._styles["section"]
                ))
            if quotations and not is_supplier:
                rows = [[
                    Paragraph("<b>N° Devis</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Date</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Statut</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Montant HT</b>", self._styles["cell_bold"]),
                ]]
                for q in quotations:
                    rows.append([
                        Paragraph(safe_text(q.get("name", "")), self._styles["cell"]),
                        Paragraph(format_date(q.get("date_order", "")), self._styles["cell"]),
                        Paragraph(
                            state_map_orders.get(q.get("state", ""), q.get("state", "")),
                            self._styles["cell"],
                        ),
                        Paragraph(
                            format_currency(q.get("amount_total", 0)),
                            self._styles["cell"],
                        ),
                    ])
                t = Table(rows, colWidths=[4 * cm, 3 * cm, 4 * cm, 4 * cm], repeatRows=1)
                t.setStyle(self._table_style_docs(len(rows), COLOR_PRIMARY))
                elements.append(t)
            elif not is_supplier:
                elements.append(Paragraph("Aucun devis.", self._styles["normal"]))

            if not is_supplier:
                elements.append(Spacer(1, 0.5 * cm))

            # ── Commandes ─────────────────────────────────────────────────────
            orders = documents.get("orders", [])
            elements.append(Paragraph(
                f"{'Commandes achat' if is_supplier else 'Commandes'} ({len(orders)})", self._styles["section"]
            ))
            if orders:
                total_commandes = sum(o.get("amount_total", 0) for o in orders)
                elements.append(Paragraph(
                    f"Total commandes : {format_currency(total_commandes)}",
                    self._styles["normal"],
                ))
                elements.append(Spacer(1, 0.2 * cm))

                rows = [[
                    Paragraph("<b>N° Commande</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Date</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Statut</b>", self._styles["cell_bold"]),
                    Paragraph(f"<b>{'Responsable' if is_supplier else 'Commercial'}</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Montant HT</b>", self._styles["cell_bold"]),
                ]]
                for o in orders:
                    user = o.get("user_id", [None, ""])
                    user_name = user[1] if isinstance(user, (list, tuple)) and len(user) > 1 else ""
                    rows.append([
                        Paragraph(safe_text(o.get("name", "")), self._styles["cell"]),
                        Paragraph(format_date(o.get("date_order", "")), self._styles["cell"]),
                        Paragraph(
                            state_map_orders.get(o.get("state", ""), o.get("state", "")),
                            self._styles["cell"],
                        ),
                        Paragraph(safe_text(user_name), self._styles["cell"]),
                        Paragraph(format_currency(o.get("amount_total", 0)), self._styles["cell"]),
                    ])
                t = Table(rows, colWidths=[3.5 * cm, 2.5 * cm, 3 * cm, 3 * cm, 3 * cm], repeatRows=1)
                t.setStyle(self._table_style_docs(len(rows), COLOR_SECONDARY))
                elements.append(t)
            else:
                elements.append(Paragraph("Aucune commande.", self._styles["normal"]))

            elements.append(Spacer(1, 0.5 * cm))

            # ── Factures ──────────────────────────────────────────────────────
            invoices = documents.get("invoices", [])
            elements.append(Paragraph(
                f"{'Factures fournisseur' if is_supplier else 'Factures'} ({len(invoices)})", self._styles["section"]
            ))
            if invoices:
                total_factures = sum(i.get("amount_total", 0) for i in invoices)
                total_du = sum(i.get("amount_residual", 0) for i in invoices)
                elements.append(Paragraph(
                    f"Total facturé : {format_currency(total_factures)} | "
                    f"Solde dû : {format_currency(total_du)}",
                    self._styles["normal"],
                ))
                elements.append(Spacer(1, 0.2 * cm))

                rows = [[
                    Paragraph("<b>N° Facture</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Date</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Échéance</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Statut</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Paiement</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Montant</b>", self._styles["cell_bold"]),
                    Paragraph("<b>Restant</b>", self._styles["cell_bold"]),
                ]]
                for inv in invoices:
                    payment_state = payment_map.get(
                        inv.get("payment_state", ""), inv.get("payment_state", "")
                    )
                    is_overdue = (
                        inv.get("payment_state") == "not_paid"
                        and inv.get("invoice_date_due")
                        and datetime.strptime(
                            str(inv["invoice_date_due"])[:10], "%Y-%m-%d"
                        ) < datetime.now()
                    )
                    rows.append([
                        Paragraph(safe_text(inv.get("name", "")), self._styles["cell"]),
                        Paragraph(format_date(inv.get("invoice_date", "")), self._styles["cell"]),
                        Paragraph(
                            f'<font color="{"red" if is_overdue else "black"}">'
                            f'{format_date(inv.get("invoice_date_due", ""))}</font>',
                            self._styles["cell"],
                        ),
                        Paragraph(
                            state_map_invoices.get(inv.get("state", ""), inv.get("state", "")),
                            self._styles["cell"],
                        ),
                        Paragraph(payment_state, self._styles["cell"]),
                        Paragraph(format_currency(inv.get("amount_total", 0)), self._styles["cell"]),
                        Paragraph(format_currency(inv.get("amount_residual", 0)), self._styles["cell"]),
                    ])
                col_w = [3 * cm, 2 * cm, 2 * cm, 2 * cm, 2.5 * cm, 2.5 * cm, 2.5 * cm]
                t = Table(rows, colWidths=col_w, repeatRows=1)
                t.setStyle(self._table_style_docs(len(rows), COLOR_RED))
                elements.append(t)
            else:
                elements.append(Paragraph("Aucune facture.", self._styles["normal"]))

            footer_cb = self._build_footer_callback()
            doc.build(elements, onFirstPage=footer_cb, onLaterPages=footer_cb)
            logger.info(f"PDF documents client généré : {filepath}")
            return filepath

        except Exception as e:
            logger.error(f"Erreur génération PDF documents client : {e}", exc_info=True)
            return None

    @staticmethod
    def _table_style_docs(num_rows: int, header_color=None) -> TableStyle:
        """Style tableau pour les documents."""
        if header_color is None:
            header_color = COLOR_PRIMARY
        style = [
            ("BACKGROUND", (0, 0), (-1, 0), header_color),
            ("TEXTCOLOR", (0, 0), (-1, 0), COLOR_WHITE),
            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, 0), 8),
            ("BOTTOMPADDING", (0, 0), (-1, 0), 5),
            ("TOPPADDING", (0, 0), (-1, 0), 5),
            ("FONTSIZE", (0, 1), (-1, -1), 7.5),
            ("TOPPADDING", (0, 1), (-1, -1), 3),
            ("BOTTOMPADDING", (0, 1), (-1, -1), 3),
            ("GRID", (0, 0), (-1, -1), 0.3, COLOR_BORDER),
            ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ]
        for i in range(1, num_rows):
            if i % 2 == 0:
                style.append(("BACKGROUND", (0, i), (-1, i), COLOR_LIGHT_GREY))
        return TableStyle(style)
