"""
Accounting PDF Report Generator
Generates professional PDF reports for Haven Grazuri Investment Limited.
Covers: Chart of Accounts, Trial Balance, Income Statement, Balance Sheet, Account Ledger.
"""
from io import BytesIO
from datetime import date, datetime
from decimal import Decimal
from typing import Optional, List, Dict, Any

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, mm
from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph,
    Spacer, PageBreak, HRFlowable, KeepTogether,
)
from reportlab.pdfgen import canvas


# ── Colour palette ─────────────────────────────────────────────────────────
C_BRAND       = colors.HexColor('#1e40af')   # brand blue
C_BRAND_LIGHT = colors.HexColor('#eff6ff')
C_BRAND_MID   = colors.HexColor('#93c5fd')
C_ASSET       = colors.HexColor('#059669')
C_LIABILITY   = colors.HexColor('#dc2626')
C_EQUITY      = colors.HexColor('#d97706')
C_INCOME      = colors.HexColor('#7c3aed')
C_EXPENSE     = colors.HexColor('#be123c')
C_PROFIT      = colors.HexColor('#059669')
C_LOSS        = colors.HexColor('#dc2626')
C_ROW_ALT     = colors.HexColor('#f9fafb')
C_GREY_LINE   = colors.HexColor('#e5e7eb')
C_TEXT        = colors.HexColor('#1f2937')
C_SUBTEXT     = colors.HexColor('#6b7280')
C_WHITE       = colors.white
C_TOTAL_BG    = colors.HexColor('#f3f4f6')


class AccountingPDFGenerator:
    """Professional PDF generator for Haven Grazuri accounting reports."""

    COMPANY_NAME    = "Haven Grazuri Investment Limited"
    COMPANY_TAGLINE = "Trusted Microfinance Partner"
    PAGE_W, PAGE_H  = A4          # 210 × 297 mm


    def __init__(self, logo_path: Optional[str] = None):
        self.logo_path = logo_path
        self.styles = getSampleStyleSheet()
        self._setup_styles()

    # ── Style setup ────────────────────────────────────────────────────────

    def _setup_styles(self):
        add = self.styles.add
        add(ParagraphStyle('CoName',   parent=self.styles['Normal'], fontSize=15,
                           textColor=C_BRAND, alignment=TA_CENTER, fontName='Helvetica-Bold', spaceAfter=2))
        add(ParagraphStyle('CoTag',    parent=self.styles['Normal'], fontSize=9,
                           textColor=C_SUBTEXT, alignment=TA_CENTER, fontName='Helvetica-Oblique', spaceAfter=4))
        add(ParagraphStyle('RptTitle', parent=self.styles['Normal'], fontSize=13,
                           textColor=C_TEXT, alignment=TA_CENTER, fontName='Helvetica-Bold', spaceAfter=2))
        add(ParagraphStyle('RptSub',   parent=self.styles['Normal'], fontSize=10,
                           textColor=C_SUBTEXT, alignment=TA_CENTER, spaceAfter=10))
        add(ParagraphStyle('SecHdr',   parent=self.styles['Normal'], fontSize=11,
                           textColor=C_WHITE, fontName='Helvetica-Bold', spaceAfter=0))
        add(ParagraphStyle('Footer',   parent=self.styles['Normal'], fontSize=7,
                           textColor=C_SUBTEXT, alignment=TA_CENTER))
        add(ParagraphStyle('Note',     parent=self.styles['Normal'], fontSize=8,
                           textColor=C_SUBTEXT, alignment=TA_LEFT))

    # ── Page callbacks ──────────────────────────────────────────────────────

    def _header_footer(self, canv: canvas.Canvas, doc):
        """Draw header and footer on every page."""
        canv.saveState()
        W = doc.width + doc.leftMargin + doc.rightMargin
        top = doc.bottomMargin + doc.height

        # --- Header stripe ---
        canv.setFillColor(C_BRAND)
        canv.rect(0, top + 4*mm, W, 12*mm, fill=1, stroke=0)

        canv.setFillColor(C_WHITE)
        canv.setFont('Helvetica-Bold', 11)
        canv.drawCentredString(W/2, top + 9*mm, self.COMPANY_NAME)
        canv.setFont('Helvetica-Oblique', 7)
        canv.drawCentredString(W/2, top + 5.5*mm, self.COMPANY_TAGLINE)

        # --- Footer ---
        canv.setFillColor(C_GREY_LINE)
        canv.rect(0, 6*mm, W, 0.3*mm, fill=1, stroke=0)
        canv.setFillColor(C_SUBTEXT)
        canv.setFont('Helvetica', 7)
        canv.drawString(doc.leftMargin, 3*mm,
                        f"Generated: {datetime.now().strftime('%d %b %Y  %H:%M')}")
        canv.drawCentredString(W/2, 3*mm, "CONFIDENTIAL — Haven Grazuri Investment Limited")
        canv.drawRightString(W - doc.rightMargin, 3*mm,
                             f"Page {canv.getPageNumber()}")
        canv.restoreState()

    # ── Helper: make a new SimpleDocTemplate ───────────────────────────────

    def _doc(self, buffer: BytesIO) -> SimpleDocTemplate:
        return SimpleDocTemplate(
            buffer, pagesize=A4,
            topMargin=20*mm, bottomMargin=18*mm,
            leftMargin=15*mm, rightMargin=15*mm,
        )

    # ── Helper: standard report header block ──────────────────────────────

    def _report_header(self, title: str, subtitle: str) -> list:
        return [
            Spacer(1, 4*mm),
            Paragraph(title, self.styles['RptTitle']),
            Paragraph(subtitle, self.styles['RptSub']),
            HRFlowable(width='100%', thickness=1, color=C_BRAND_MID),
            Spacer(1, 4*mm),
        ]

    # ── Helper: section heading row ────────────────────────────────────────

    def _section_table(self, label: str, bg_color: colors.Color,
                       right_text: str = '') -> Table:
        data = [[Paragraph(label, self.styles['SecHdr']),
                 Paragraph(right_text, ParagraphStyle(
                     'SHR', parent=self.styles['SecHdr'],
                     alignment=TA_RIGHT))]]
        t = Table(data, colWidths=['70%', '30%'])
        t.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, -1), bg_color),
            ('TOPPADDING',    (0, 0), (-1, -1), 5),
            ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
            ('LEFTPADDING',   (0, 0), (0, -1),  8),
            ('RIGHTPADDING',  (-1, 0), (-1, -1), 8),
        ]))
        return t

    # ── Helper: money table body ───────────────────────────────────────────

    def _money_table(self, rows: list, col_widths: list,
                     header: list = None, accent: colors.Color = C_BRAND) -> Table:
        """
        rows    – list of [col1, col2, ...] already formatted strings.
        header  – optional header row list.
        """
        data = ([header] if header else []) + rows
        t = Table(data, colWidths=col_widths, repeatRows=1 if header else 0)

        base_style = [
            ('FONTNAME',      (0, 0), (-1, -1), 'Helvetica'),
            ('FONTSIZE',      (0, 0), (-1, -1), 8),
            ('TOPPADDING',    (0, 0), (-1, -1), 4),
            ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
            ('LEFTPADDING',   (0, 0), (-1, -1), 6),
            ('RIGHTPADDING',  (0, 0), (-1, -1), 6),
            ('ROWBACKGROUNDS', (0, 1 if header else 0), (-1, -1),
             [C_WHITE, C_ROW_ALT]),
            ('LINEBELOW', (0, 0), (-1, -1), 0.3, C_GREY_LINE),
        ]
        if header:
            base_style += [
                ('BACKGROUND',  (0, 0), (-1, 0), accent),
                ('TEXTCOLOR',   (0, 0), (-1, 0), C_WHITE),
                ('FONTNAME',    (0, 0), (-1, 0), 'Helvetica-Bold'),
                ('FONTSIZE',    (0, 0), (-1, 0), 8),
                ('LINEBELOW',   (0, 0), (-1, 0), 1, accent),
            ]
        t.setStyle(TableStyle(base_style))
        return t

    @staticmethod
    def _fmt(amount: Decimal) -> str:
        """Format Decimal as KES with thousands separator."""
        if amount is None:
            return '-'
        neg = amount < 0
        abs_str = f"{abs(amount):,.2f}"
        return f"({abs_str})" if neg else abs_str

    @staticmethod
    def _pct(amount: Decimal, total: Decimal) -> str:
        if not total or total == 0:
            return '-'
        return f"{(amount / total * 100):.1f}%"


    # ══════════════════════════════════════════════════════════════════════
    # 1. CHART OF ACCOUNTS PDF
    # ══════════════════════════════════════════════════════════════════════

    def generate_chart_of_accounts_pdf(
        self,
        accounts_by_type: Dict[str, List[Dict]],
        as_of_date: date,
        include_balances: bool = True,
    ) -> BytesIO:
        buffer = BytesIO()
        doc = self._doc(buffer)
        elems = []

        elems += self._report_header(
            "Chart of Accounts",
            f"All active general ledger accounts  •  Balances as of {as_of_date.strftime('%d %B %Y')}",
        )

        # Summary counts
        total = sum(len(v) for v in accounts_by_type.values())
        sum_row = [[t.title(), str(len(accounts_by_type.get(t, [])))]
                   for t in ['asset','liability','equity','income','expense']]
        sum_row.append(['TOTAL', str(total)])
        st = Table(sum_row, colWidths=[80*mm, 30*mm])
        st.setStyle(TableStyle([
            ('FONTNAME',  (0,0),(-1,-2), 'Helvetica'), ('FONTSIZE',(0,0),(-1,-1),8),
            ('FONTNAME',  (0,-1),(-1,-1),'Helvetica-Bold'),
            ('BACKGROUND',(0,-1),(-1,-1), C_BRAND_LIGHT),
            ('LINEBELOW', (0,0),(-1,-2), 0.3, C_GREY_LINE),
            ('LINEABOVE', (0,-1),(-1,-1), 1, C_BRAND),
            ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
        ]))
        elems += [st, Spacer(1, 5*mm)]

        # One section per account type
        type_cfg = [
            ('asset',     'ASSETS',      C_ASSET),
            ('liability', 'LIABILITIES', C_LIABILITY),
            ('equity',    'EQUITY',      C_EQUITY),
            ('income',    'INCOME',      C_INCOME),
            ('expense',   'EXPENSES',    C_EXPENSE),
        ]
        for acc_type, label, colour in type_cfg:
            accounts = accounts_by_type.get(acc_type, [])
            if not accounts:
                continue
            elems.append(self._section_table(
                f"{label}  ({len(accounts)} accounts)", colour,
                right_text="Debit Balance" if acc_type in ('asset','expense') else "Credit Balance"
            ))

            if include_balances:
                hdr = ['Code', 'Account Name', 'Subtype', 'Balance (KES)']
                cw  = [22*mm, 95*mm, 30*mm, 28*mm]
            else:
                hdr = ['Code', 'Account Name', 'Subtype']
                cw  = [22*mm, 110*mm, 43*mm]

            rows = []
            section_total = Decimal('0.00')
            for acc in accounts:
                bal = acc.get('balance', Decimal('0.00'))
                section_total += abs(bal)
                row = [acc['code'], acc['name'],
                       (acc.get('subtype') or '').replace('_', ' ').title()]
                if include_balances:
                    row.append(self._fmt(abs(bal)))
                rows.append(row)

            if include_balances:
                rows.append(['', 'SUBTOTAL', '', self._fmt(section_total)])

            t = self._money_table(rows, cw, header=hdr, accent=colour)
            # Bold the subtotal row
            t.setStyle(TableStyle([
                ('FONTNAME',    (0,-1),(-1,-1), 'Helvetica-Bold'),
                ('BACKGROUND',  (0,-1),(-1,-1), C_TOTAL_BG),
                ('LINEABOVE',   (0,-1),(-1,-1), 1, colour),
                ('ALIGN',       (3,0),(3,-1),   'RIGHT') if include_balances else ('ALIGN',(2,0),(2,-1),'RIGHT'),
                ('ALIGN',       (0,1),(0,-1),   'CENTER'),
            ]))
            elems += [t, Spacer(1, 5*mm)]

        # Footer note
        elems.append(Paragraph(
            "Note: Balances reflect posted journal entries as of the report date. "
            "Contact your accountant for adjusting entries.",
            self.styles['Note']
        ))

        doc.build(elems, onFirstPage=self._header_footer, onLaterPages=self._header_footer)
        buffer.seek(0)
        return buffer


    # ══════════════════════════════════════════════════════════════════════
    # 2. TRIAL BALANCE PDF
    # ══════════════════════════════════════════════════════════════════════

    def generate_trial_balance_pdf(
        self,
        trial_balance_data: Dict,
        as_of_date: date,
    ) -> BytesIO:
        buffer = BytesIO()
        doc = self._doc(buffer)
        elems = []

        totals   = trial_balance_data.get('totals', {})
        td       = totals.get('total_debits',  Decimal('0.00'))
        tc       = totals.get('total_credits', Decimal('0.00'))
        balanced = totals.get('balanced', False)
        accounts = trial_balance_data.get('accounts', [])

        elems += self._report_header(
            "Trial Balance",
            f"As of  {as_of_date.strftime('%d %B %Y')}  •  "
            f"{'✓ BALANCED' if balanced else '⚠ OUT OF BALANCE'}",
        )

        # Balance indicator
        ind_bg = colors.HexColor('#d1fae5') if balanced else colors.HexColor('#fee2e2')
        ind_fg = C_PROFIT if balanced else C_LOSS
        msg    = ("All debits equal all credits — books are in order."
                  if balanced else
                  f"Difference of KES {self._fmt(abs(td-tc))} detected. Please review journal entries.")
        ind_t  = Table([[Paragraph(msg, ParagraphStyle(
            'Ind', parent=self.styles['Normal'], fontSize=9, textColor=ind_fg, fontName='Helvetica-Bold'
        ))]], colWidths=['100%'])
        ind_t.setStyle(TableStyle([
            ('BACKGROUND', (0,0),(-1,-1), ind_bg),
            ('TOPPADDING', (0,0),(-1,-1), 6), ('BOTTOMPADDING',(0,0),(-1,-1),6),
            ('LEFTPADDING',(0,0),(-1,-1), 10),
            ('BOX', (0,0),(-1,-1), 1, ind_fg),
        ]))
        elems += [ind_t, Spacer(1, 5*mm)]

        # Group accounts by type for sub-totals
        type_order = ['asset','liability','equity','income','expense']
        by_type: Dict[str, list] = {t: [] for t in type_order}
        for acc in accounts:
            t = acc.get('type', '')
            if t in by_type:
                by_type[t].append(acc)

        type_colours = {
            'asset': C_ASSET, 'liability': C_LIABILITY, 'equity': C_EQUITY,
            'income': C_INCOME, 'expense': C_EXPENSE,
        }
        cw  = [22*mm, 90*mm, 28*mm, 28*mm]
        hdr = ['Code', 'Account Name', 'Debit (KES)', 'Credit (KES)']

        grand_d = Decimal('0.00')
        grand_c = Decimal('0.00')

        for acc_type in type_order:
            accs = by_type[acc_type]
            if not accs:
                continue
            colour = type_colours[acc_type]
            elems.append(self._section_table(acc_type.upper(), colour))

            rows = []
            sub_d = Decimal('0.00')
            sub_c = Decimal('0.00')
            for acc in accs:
                d = acc.get('debit_balance',  Decimal('0.00'))
                c = acc.get('credit_balance', Decimal('0.00'))
                sub_d += d; sub_c += c
                rows.append([
                    acc['code'], acc['name'],
                    self._fmt(d) if d else '-',
                    self._fmt(c) if c else '-',
                ])
            rows.append(['', f'Subtotal — {acc_type.title()}',
                         self._fmt(sub_d), self._fmt(sub_c)])
            grand_d += sub_d; grand_c += sub_c

            t = self._money_table(rows, cw, header=hdr, accent=colour)
            t.setStyle(TableStyle([
                ('ALIGN',      (2,0),(-1,-1),  'RIGHT'),
                ('FONTNAME',   (0,-1),(-1,-1), 'Helvetica-Bold'),
                ('BACKGROUND', (0,-1),(-1,-1), C_TOTAL_BG),
                ('LINEABOVE',  (0,-1),(-1,-1), 1, colour),
            ]))
            elems += [t, Spacer(1, 4*mm)]

        # Grand total
        gt = Table([
            ['TOTAL', '', self._fmt(grand_d), self._fmt(grand_c)],
        ], colWidths=cw)
        gt.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-1), 'Helvetica-Bold'),
            ('FONTSIZE',    (0,0),(-1,-1), 10),
            ('BACKGROUND',  (0,0),(-1,-1), C_BRAND),
            ('TEXTCOLOR',   (0,0),(-1,-1), C_WHITE),
            ('ALIGN',       (2,0),(-1,-1), 'RIGHT'),
            ('TOPPADDING',  (0,0),(-1,-1), 7),
            ('BOTTOMPADDING',(0,0),(-1,-1),7),
        ]))
        elems.append(gt)
        doc.build(elems, onFirstPage=self._header_footer, onLaterPages=self._header_footer)
        buffer.seek(0)
        return buffer


    # ══════════════════════════════════════════════════════════════════════
    # 3. INCOME STATEMENT PDF
    # ══════════════════════════════════════════════════════════════════════

    def generate_income_statement_pdf(
        self,
        income_data: Dict,
        start_date: date,
        end_date: date,
    ) -> BytesIO:
        buffer = BytesIO()
        doc = self._doc(buffer)
        elems = []

        period_str = (f"{start_date.strftime('%d %b %Y')}  →  "
                      f"{end_date.strftime('%d %b %Y')}")
        elems += self._report_header("Income Statement (Profit & Loss)", f"Period: {period_str}")

        income_items  = income_data.get('income',   [])
        expense_items = income_data.get('expenses', [])
        total_income   = sum(i.get('amount', Decimal('0.00')) for i in income_items)
        total_expenses = sum(i.get('amount', Decimal('0.00')) for i in expense_items)
        net_income     = total_income - total_expenses
        profit         = net_income >= 0

        cw  = [22*mm, 90*mm, 30*mm, 26*mm]
        hdr = ['Code', 'Account Name', 'Amount (KES)', '% of Revenue']

        # ── Revenue section ───────────────────────────────────────────────
        elems.append(self._section_table("REVENUE & INCOME", C_INCOME))
        rows = []
        for item in income_items:
            rows.append([
                item.get('code',''),
                item.get('name',''),
                self._fmt(item.get('amount', Decimal('0.00'))),
                self._pct(item.get('amount', Decimal('0.00')), total_income),
            ])
        rows.append(['', 'TOTAL REVENUE', self._fmt(total_income), '100.0%'])
        t = self._money_table(rows, cw, header=hdr, accent=C_INCOME)
        t.setStyle(TableStyle([
            ('ALIGN', (2,0),(-1,-1), 'RIGHT'),
            ('FONTNAME',   (0,-1),(-1,-1), 'Helvetica-Bold'),
            ('BACKGROUND', (0,-1),(-1,-1), colors.HexColor('#ede9fe')),
            ('LINEABOVE',  (0,-1),(-1,-1), 1, C_INCOME),
        ]))
        elems += [t, Spacer(1, 5*mm)]

        # ── Expenses section ──────────────────────────────────────────────
        elems.append(self._section_table("OPERATING EXPENSES", C_EXPENSE))
        rows = []
        for item in expense_items:
            rows.append([
                item.get('code',''),
                item.get('name',''),
                self._fmt(item.get('amount', Decimal('0.00'))),
                self._pct(item.get('amount', Decimal('0.00')), total_income),
            ])
        rows.append(['', 'TOTAL EXPENSES', self._fmt(total_expenses),
                     self._pct(total_expenses, total_income)])
        t = self._money_table(rows, cw, header=hdr, accent=C_EXPENSE)
        t.setStyle(TableStyle([
            ('ALIGN', (2,0),(-1,-1), 'RIGHT'),
            ('FONTNAME',   (0,-1),(-1,-1), 'Helvetica-Bold'),
            ('BACKGROUND', (0,-1),(-1,-1), colors.HexColor('#fce7f3')),
            ('LINEABOVE',  (0,-1),(-1,-1), 1, C_EXPENSE),
        ]))
        elems += [t, Spacer(1, 6*mm)]

        # ── Summary block ─────────────────────────────────────────────────
        margin_pct = self._pct(net_income, total_income) if total_income else '-'
        expense_ratio = self._pct(total_expenses, total_income) if total_income else '-'
        net_colour = C_PROFIT if profit else C_LOSS
        net_label  = "NET PROFIT" if profit else "NET LOSS"

        summary_rows = [
            ['Total Revenue',  self._fmt(total_income)],
            ['Less: Total Expenses', f"({self._fmt(total_expenses)})"],
            [net_label, self._fmt(abs(net_income))],
        ]
        sm = Table(summary_rows, colWidths=[110*mm, 40*mm])
        sm.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-2),  'Helvetica'),
            ('FONTNAME',    (0,-1),(-1,-1), 'Helvetica-Bold'),
            ('FONTSIZE',    (0,0),(-1,-1),  10),
            ('ALIGN',       (1,0),(1,-1),   'RIGHT'),
            ('BACKGROUND',  (0,-1),(-1,-1), net_colour),
            ('TEXTCOLOR',   (0,-1),(-1,-1), C_WHITE),
            ('TOPPADDING',  (0,-1),(-1,-1), 8),
            ('BOTTOMPADDING',(0,-1),(-1,-1),8),
            ('TOPPADDING',  (0,0),(-1,-2),  5),
            ('BOTTOMPADDING',(0,0),(-1,-2), 5),
            ('LINEABOVE',   (0,0),(-1,0),   1.5, C_BRAND),
            ('LINEBELOW',   (0,-2),(-1,-2), 1, C_GREY_LINE),
            ('BOX',         (0,0),(-1,-1),  0.5, C_GREY_LINE),
        ]))
        elems += [sm, Spacer(1, 5*mm)]

        # ── Key ratios ────────────────────────────────────────────────────
        kpi_rows = [
            ['Profit Margin', margin_pct, 'Expense Ratio', expense_ratio],
        ]
        kt = Table(kpi_rows, colWidths=[40*mm, 35*mm, 40*mm, 35*mm])
        kt.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-1), 'Helvetica'),
            ('FONTSIZE',    (0,0),(-1,-1), 8),
            ('BACKGROUND',  (0,0),(-1,-1), C_BRAND_LIGHT),
            ('ALIGN',       (1,0),(1,-1),  'CENTER'),
            ('ALIGN',       (3,0),(3,-1),  'CENTER'),
            ('FONTNAME',    (1,0),(1,-1),  'Helvetica-Bold'),
            ('FONTNAME',    (3,0),(3,-1),  'Helvetica-Bold'),
            ('TOPPADDING',  (0,0),(-1,-1), 5),
            ('BOTTOMPADDING',(0,0),(-1,-1),5),
            ('BOX',         (0,0),(-1,-1), 0.5, C_BRAND_MID),
        ]))
        elems += [kt, Spacer(1, 4*mm)]
        elems.append(Paragraph(
            "Note: Period activity shown (revenue earned and expenses incurred during the period, "
            "not cumulative balances).", self.styles['Note']
        ))

        doc.build(elems, onFirstPage=self._header_footer, onLaterPages=self._header_footer)
        buffer.seek(0)
        return buffer


    # ══════════════════════════════════════════════════════════════════════
    # 4. BALANCE SHEET PDF
    # ══════════════════════════════════════════════════════════════════════

    def generate_balance_sheet_pdf(
        self,
        balance_data: Dict,
        as_of_date: date,
    ) -> BytesIO:
        buffer = BytesIO()
        doc = self._doc(buffer)
        elems = []

        asset_items     = balance_data.get('assets',      [])
        liability_items = balance_data.get('liabilities', [])
        equity_items    = balance_data.get('equity',      [])

        total_assets      = sum(i.get('amount', Decimal('0.00')) for i in asset_items)
        total_liabilities = sum(i.get('amount', Decimal('0.00')) for i in liability_items)
        total_equity      = sum(i.get('amount', Decimal('0.00')) for i in equity_items)
        total_le          = total_liabilities + total_equity
        balanced          = abs(total_assets - total_le) < Decimal('0.01')

        elems += self._report_header(
            "Balance Sheet (Statement of Financial Position)",
            f"As of  {as_of_date.strftime('%d %B %Y')}  •  "
            f"{'A = L + E  ✓ BALANCED' if balanced else '⚠ OUT OF BALANCE'}",
        )

        cw  = [22*mm, 95*mm, 28*mm, 25*mm]
        hdr = ['Code', 'Account Name', 'Amount (KES)', '% of Total']

        def _section(items, label, colour, total_label, total_val, pct_base):
            elems.append(self._section_table(label, colour))
            rows = []
            for item in items:
                rows.append([
                    item.get('code',''),
                    item.get('name',''),
                    self._fmt(item.get('amount', Decimal('0.00'))),
                    self._pct(item.get('amount', Decimal('0.00')), pct_base),
                ])
            rows.append(['', total_label, self._fmt(total_val),
                         self._pct(total_val, pct_base)])
            t = self._money_table(rows, cw, header=hdr, accent=colour)
            t.setStyle(TableStyle([
                ('ALIGN',      (2,0),(-1,-1),  'RIGHT'),
                ('FONTNAME',   (0,-1),(-1,-1), 'Helvetica-Bold'),
                ('BACKGROUND', (0,-1),(-1,-1), C_TOTAL_BG),
                ('LINEABOVE',  (0,-1),(-1,-1), 1, colour),
            ]))
            elems.append(t)
            elems.append(Spacer(1, 5*mm))

        _section(asset_items,     "ASSETS",      C_ASSET,
                 "TOTAL ASSETS",      total_assets,      total_assets)
        _section(liability_items, "LIABILITIES", C_LIABILITY,
                 "TOTAL LIABILITIES", total_liabilities, total_assets)
        _section(equity_items,    "EQUITY",      C_EQUITY,
                 "TOTAL EQUITY",      total_equity,      total_assets)

        # ── Equation check ────────────────────────────────────────────────
        eq_colour = C_PROFIT if balanced else C_LOSS
        eq_rows = [
            ['Total Assets',            self._fmt(total_assets)],
            ['Total Liabilities',       self._fmt(total_liabilities)],
            ['Total Equity',            self._fmt(total_equity)],
            ['Total Liabilities + Equity', self._fmt(total_le)],
            ['Difference (should be zero)', self._fmt(abs(total_assets - total_le))],
        ]
        eq = Table(eq_rows, colWidths=[110*mm, 40*mm])
        eq.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-1),  'Helvetica'),
            ('FONTSIZE',    (0,0),(-1,-1),  9),
            ('ALIGN',       (1,0),(1,-1),   'RIGHT'),
            ('LINEBELOW',   (0,0),(-1,-2),  0.3, C_GREY_LINE),
            ('FONTNAME',    (0,-1),(-1,-1), 'Helvetica-Bold'),
            ('TEXTCOLOR',   (0,-1),(-1,-1), eq_colour),
            ('TOPPADDING',  (0,0),(-1,-1),  4),
            ('BOTTOMPADDING',(0,0),(-1,-1), 4),
            ('BOX',         (0,0),(-1,-1),  0.5, C_GREY_LINE),
        ]))
        elems += [eq, Spacer(1, 4*mm)]

        # ── Key ratios ────────────────────────────────────────────────────
        eq_ratio   = self._pct(total_equity,      total_assets) if total_assets else '-'
        debt_ratio = self._pct(total_liabilities, total_assets) if total_assets else '-'
        dte        = (f"{(total_liabilities/total_equity):.2f}x"
                      if total_equity and total_equity > 0 else '-')
        kpi_rows = [['Equity Ratio', eq_ratio, 'Debt Ratio', debt_ratio,
                     'Debt-to-Equity', dte]]
        kt = Table(kpi_rows, colWidths=[30*mm,22*mm,27*mm,22*mm,30*mm,22*mm])
        kt.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-1), 'Helvetica'), ('FONTSIZE',(0,0),(-1,-1),8),
            ('BACKGROUND',  (0,0),(-1,-1), C_BRAND_LIGHT),
            ('ALIGN',       (1,0),(1,-1),  'CENTER'),
            ('ALIGN',       (3,0),(3,-1),  'CENTER'),
            ('ALIGN',       (5,0),(5,-1),  'CENTER'),
            ('FONTNAME',    (1,0),(1,-1),  'Helvetica-Bold'),
            ('FONTNAME',    (3,0),(3,-1),  'Helvetica-Bold'),
            ('FONTNAME',    (5,0),(5,-1),  'Helvetica-Bold'),
            ('TOPPADDING',  (0,0),(-1,-1), 5), ('BOTTOMPADDING',(0,0),(-1,-1),5),
            ('BOX',         (0,0),(-1,-1), 0.5, C_BRAND_MID),
        ]))
        elems += [kt, Spacer(1,4*mm)]
        elems.append(Paragraph(
            "Assets = Liabilities + Equity (Fundamental accounting equation). "
            "Equity Ratio shows the proportion of assets financed by owners.",
            self.styles['Note']
        ))

        doc.build(elems, onFirstPage=self._header_footer, onLaterPages=self._header_footer)
        buffer.seek(0)
        return buffer


    # ══════════════════════════════════════════════════════════════════════
    # 5. ACCOUNT LEDGER PDF
    # ══════════════════════════════════════════════════════════════════════

    def generate_account_ledger_pdf(
        self,
        account: Dict,
        transactions: List[Dict],
        start_date: Optional[date] = None,
        end_date:   Optional[date] = None,
        opening_balance: Decimal = Decimal('0.00'),
    ) -> BytesIO:
        buffer = BytesIO()
        doc    = self._doc(buffer)
        elems  = []

        period_str = ''
        if start_date and end_date:
            period_str = (f"{start_date.strftime('%d %b %Y')}  →  "
                          f"{end_date.strftime('%d %b %Y')}")
        elif end_date:
            period_str = f"Up to {end_date.strftime('%d %b %Y')}"

        elems += self._report_header(
            f"Account Ledger — {account.get('code','')} · {account.get('name','')}",
            f"{period_str}   |   Type: {account.get('type','').title()}   "
            f"|   Subtype: {(account.get('subtype') or '').replace('_',' ').title()}",
        )

        # Summary bar
        total_d = Decimal('0.00')
        total_c = Decimal('0.00')
        for tx in transactions:
            total_d += tx.get('debit',  tx.get('debit_amount',  Decimal('0.00'))) or Decimal('0.00')
            total_c += tx.get('credit', tx.get('credit_amount', Decimal('0.00'))) or Decimal('0.00')

        closing_balance = opening_balance + total_d - total_c

        sum_rows = [
            ['Opening Balance', self._fmt(opening_balance),
             'Total Debits',    self._fmt(total_d)],
            ['Closing Balance', self._fmt(closing_balance),
             'Total Credits',   self._fmt(total_c)],
            ['Transactions',    str(len(transactions)),
             'Net Movement',    self._fmt(total_d - total_c)],
        ]
        st = Table(sum_rows, colWidths=[35*mm,30*mm,35*mm,30*mm])
        st.setStyle(TableStyle([
            ('FONTNAME',    (0,0),(-1,-1), 'Helvetica'), ('FONTSIZE',(0,0),(-1,-1),8),
            ('FONTNAME',    (1,0),(1,-1),  'Helvetica-Bold'),
            ('FONTNAME',    (3,0),(3,-1),  'Helvetica-Bold'),
            ('BACKGROUND',  (0,0),(-1,-1), C_BRAND_LIGHT),
            ('ALIGN',       (1,0),(1,-1),  'RIGHT'),
            ('ALIGN',       (3,0),(3,-1),  'RIGHT'),
            ('TOPPADDING',  (0,0),(-1,-1), 4), ('BOTTOMPADDING',(0,0),(-1,-1),4),
            ('BOX',         (0,0),(-1,-1), 0.5, C_BRAND_MID),
            ('LINEBELOW',   (0,0),(-1,-2), 0.3, C_GREY_LINE),
        ]))
        elems += [st, Spacer(1, 5*mm)]

        # Transactions table
        elems.append(self._section_table("TRANSACTION DETAIL", C_BRAND))

        cw  = [22*mm, 68*mm, 24*mm, 24*mm, 24*mm]
        hdr = ['Date', 'Description', 'Debit (KES)', 'Credit (KES)', 'Balance (KES)']

        rows = []
        # Opening balance row
        rows.append([
            start_date.strftime('%d %b %Y') if start_date else '—',
            'Opening Balance',
            '', '',
            self._fmt(opening_balance),
        ])

        for tx in transactions:
            d_amt = tx.get('debit',  tx.get('debit_amount',  Decimal('0.00'))) or Decimal('0.00')
            c_amt = tx.get('credit', tx.get('credit_amount', Decimal('0.00'))) or Decimal('0.00')
            bal   = tx.get('balance', Decimal('0.00'))
            tx_dt = tx.get('date', tx.get('transaction_date', ''))
            if hasattr(tx_dt, 'strftime'):
                tx_dt = tx_dt.strftime('%d %b %Y')
            rows.append([
                str(tx_dt),
                str(tx.get('description', ''))[:55],
                self._fmt(d_amt) if d_amt else '-',
                self._fmt(c_amt) if c_amt else '-',
                self._fmt(bal),
            ])

        # Closing balance row
        rows.append([
            end_date.strftime('%d %b %Y') if end_date else '—',
            'Closing Balance',
            self._fmt(total_d),
            self._fmt(total_c),
            self._fmt(closing_balance),
        ])

        t = self._money_table(rows, cw, header=hdr, accent=C_BRAND)
        t.setStyle(TableStyle([
            ('ALIGN',       (2,0),(-1,-1),  'RIGHT'),
            ('FONTNAME',    (0,1),(0,1),    'Helvetica-Bold'),    # opening row
            ('FONTNAME',    (0,-1),(-1,-1), 'Helvetica-Bold'),   # closing row
            ('BACKGROUND',  (0,1),(-1,1),   C_BRAND_LIGHT),
            ('BACKGROUND',  (0,-1),(-1,-1), C_BRAND_LIGHT),
        ]))
        elems += [t, Spacer(1, 4*mm)]
        elems.append(Paragraph(
            "Debit entries increase asset/expense accounts. "
            "Credit entries increase liability/equity/income accounts.",
            self.styles['Note']
        ))

        doc.build(elems, onFirstPage=self._header_footer, onLaterPages=self._header_footer)
        buffer.seek(0)
        return buffer
