"""
Reporting utilities for generating PDF documents.

Provides PDF builders used by report endpoints (phone book, payments detailed).
Uses fpdf2 to generate simple tabular PDFs with automatic pagination.
"""

from __future__ import annotations

from datetime import date
from io import BytesIO
from typing import Iterable, List, Dict, Any, Tuple

from fpdf import FPDF
import structlog

# Local imports are type-only to avoid circular import costs at import time
from .models import Client, Payment


logger = structlog.get_logger(__name__)


class SimplePDF(FPDF):
    """Small helper subclass to set defaults and provide header/footer hooks."""

    def __init__(self, title: str):
        super().__init__(orientation="P", unit="mm", format="Letter")
        self.title = title
        self.set_auto_page_break(auto=True, margin=15)
        self.set_margins(left=12, top=12, right=12)

    def header(self):  # type: ignore[override]
        self.set_font("helvetica", "B", 12)
        self.cell(0, 8, self.title, ln=1, align="L")
        self.ln(2)

    def footer(self):  # type: ignore[override]
        self.set_y(-12)
        self.set_font("helvetica", size=8)
        self.set_text_color(120)
        self.cell(0, 8, f"Page {self.page_no()}", align="R")


def _output_pdf_bytes(pdf: FPDF) -> bytes:
    """Return the PDF content as bytes.

    fpdf2's output(dest='S') returns a str; encode to latin-1 per fpdf guidance.
    """
    content_str = pdf.output(dest="S")  # type: ignore[no-untyped-call]
    if isinstance(content_str, bytes):
        return content_str
    return content_str.encode("latin-1")


def build_phone_book_pdf(clients: List[Client]) -> bytes:
    """Build a Phone Book PDF from a list of `Client` records with phones."""
    logger.info("pdf_phone_book_start", count=len(clients))

    pdf = SimplePDF(title="Phone Book")
    pdf.add_page()

    # Table header
    pdf.set_font("helvetica", "B", 10)
    headers = ["Name", "Company", "Phone Type", "Phone Number"]
    widths = [55, 55, 35, 45]
    for h, w in zip(headers, widths):
        pdf.cell(w, 8, h, border=1)
    pdf.ln(8)

    pdf.set_font("helvetica", size=10)
    for client in clients:
        rows: List[Tuple[str, str, str, str]] = []
        name = f"{client.last_name or ''}, {client.first_name or ''}".strip(", ")
        company = client.company or ""
        if getattr(client, "phones", None):
            for p in client.phones:  # type: ignore[attr-defined]
                rows.append((name, company, p.phone_type or "", p.phone_number or ""))
        else:
            rows.append((name, company, "", ""))

        for c0, c1, c2, c3 in rows:
            pdf.cell(widths[0], 7, c0[:35], border=1)
            pdf.cell(widths[1], 7, c1[:35], border=1)
            pdf.cell(widths[2], 7, c2[:18], border=1)
            pdf.cell(widths[3], 7, c3[:24], border=1)
            pdf.ln(7)

    logger.info("pdf_phone_book_done", pages=pdf.page_no())
    return _output_pdf_bytes(pdf)


def build_payments_detailed_pdf(payments: List[Payment]) -> bytes:
    """Build a Payments - Detailed PDF grouped by deposit (payment) date.

    Groups by date portion of `payment_date`. Includes per-day totals and overall total.
    """
    logger.info("pdf_payments_detailed_start", count=len(payments))

    # Group payments by date
    grouped: Dict[date, List[Payment]] = {}
    for p in payments:
        d = p.payment_date.date() if p.payment_date else None
        if d is None:
            # Place undated at epoch-ish bucket None-equivalent: skip grouping
            continue
        grouped.setdefault(d, []).append(p)

    dates_sorted = sorted(grouped.keys())
    overall_total = sum((p.amount or 0.0) for p in payments)

    pdf = SimplePDF(title="Payments - Detailed")
    pdf.add_page()

    pdf.set_font("helvetica", size=10)
    pdf.cell(0, 6, f"Total Amount: ${overall_total:,.2f}", ln=1)
    pdf.ln(1)

    for d in dates_sorted:
        day_items = grouped[d]
        day_total = sum((p.amount or 0.0) for p in day_items)

        # Section header per date
        pdf.set_font("helvetica", "B", 11)
        pdf.cell(0, 7, f"Deposit Date: {d.isoformat()}  —  Total: ${day_total:,.2f}", ln=1)

        # Table header
        pdf.set_font("helvetica", "B", 10)
        headers = ["File #", "Client", "Type", "Description", "Amount"]
        widths = [28, 50, 18, 80, 18]
        for h, w in zip(headers, widths):
            pdf.cell(w, 7, h, border=1)
        pdf.ln(7)

        pdf.set_font("helvetica", size=10)
        for p in day_items:
            file_no = p.case.file_no if p.case else ""
            client = ""
            if p.case and p.case.client:
                client = f"{p.case.client.last_name or ''}, {p.case.client.first_name or ''}".strip(", ")
            ptype = p.payment_type or ""
            desc = (p.description or "").replace("\n", " ")
            amt = f"${(p.amount or 0.0):,.2f}"

            # Row cells
            pdf.cell(widths[0], 6, file_no[:14], border=1)
            pdf.cell(widths[1], 6, client[:28], border=1)
            pdf.cell(widths[2], 6, ptype[:8], border=1)

            # Description as MultiCell: compute remaining width before amount
            x_before = pdf.get_x()
            y_before = pdf.get_y()
            pdf.multi_cell(widths[3], 6, desc[:300], border=1)
            # Move to amount cell position (right side) aligning with the top of description row
            x_after = x_before + widths[3] + widths[0] + widths[1] + widths[2]
            # Reset cursor to top of the description cell's first line row to draw amount
            pdf.set_xy(x_after, y_before)
            pdf.cell(widths[4], 6, amt, border=1, align="R")
            pdf.ln(0)  # continue after multicell handled line advance

        pdf.ln(3)

    logger.info("pdf_payments_detailed_done", pages=pdf.page_no())
    return _output_pdf_bytes(pdf)


# ------------------------------
# Additional PDF Builders
# ------------------------------

def _format_client_name(client: Client) -> str:
    last = client.last_name or ""
    first = client.first_name or ""
    name = f"{last}, {first}".strip(", ")
    return name or (client.company or "")


def _format_city_state_zip(client: Client) -> str:
    parts: list[str] = []
    if client.city:
        parts.append(client.city)
    state_zip = " ".join([p for p in [(client.state or ""), (client.zip_code or "")] if p])
    if state_zip:
        if parts:
            parts[-1] = f"{parts[-1]},"
        parts.append(state_zip)
    return " ".join(parts)


def build_envelope_pdf(clients: List[Client]) -> bytes:
    """Build an Envelope PDF with mailing blocks per client.

    Layout uses a simple grid to place multiple #10 envelope-style address
    blocks per Letter page. Each block includes:
    - Name (Last, First)
    - Company (if present)
    - Address line
    - City, ST ZIP
    """
    logger.info("pdf_envelope_start", count=len(clients))

    pdf = SimplePDF(title="Envelope Blocks")
    pdf.add_page()

    # Grid parameters
    usable_width = pdf.w - pdf.l_margin - pdf.r_margin
    usable_height = pdf.h - pdf.t_margin - pdf.b_margin
    cols = 2
    col_w = usable_width / cols
    row_h = 45  # mm per block
    rows = max(1, int(usable_height // row_h))

    pdf.set_font("helvetica", size=11)

    col = 0
    row = 0
    for idx, c in enumerate(clients):
        if row >= rows:
            # next page
            pdf.add_page()
            col = 0
            row = 0

        x = pdf.l_margin + (col * col_w) + 6  # slight inner padding
        y = pdf.t_margin + (row * row_h) + 8

        # Draw block contents
        pdf.set_xy(x, y)
        name_line = _format_client_name(c)
        if name_line:
            pdf.cell(col_w - 12, 6, name_line, ln=1)
        if c.company:
            pdf.set_x(x)
            pdf.cell(col_w - 12, 6, c.company[:48], ln=1)
        if c.address:
            pdf.set_x(x)
            pdf.cell(col_w - 12, 6, c.address[:48], ln=1)
        city_state_zip = _format_city_state_zip(c)
        if city_state_zip:
            pdf.set_x(x)
            pdf.cell(col_w - 12, 6, city_state_zip[:48], ln=1)

        # Advance grid position
        col += 1
        if col >= cols:
            col = 0
            row += 1

    logger.info("pdf_envelope_done", pages=pdf.page_no())
    return _output_pdf_bytes(pdf)


def build_phone_book_address_pdf(clients: List[Client]) -> bytes:
    """Build a Phone Book (Address + Phone) PDF.

    Columns: Name, Company, Address, City, State, ZIP, Phone
    Multiple phone numbers yield multiple rows per client.
    """
    logger.info("pdf_phone_book_addr_start", count=len(clients))

    pdf = SimplePDF(title="Phone Book — Address + Phone")
    pdf.add_page()

    headers = ["Name", "Company", "Address", "City", "State", "ZIP", "Phone"]
    widths = [40, 40, 55, 28, 12, 18, 30]

    pdf.set_font("helvetica", "B", 9)
    for h, w in zip(headers, widths):
        pdf.cell(w, 7, h, border=1)
    pdf.ln(7)

    pdf.set_font("helvetica", size=9)
    for c in clients:
        name = _format_client_name(c)
        phones = getattr(c, "phones", None) or []  # type: ignore[attr-defined]
        if phones:
            for p in phones:
                pdf.cell(widths[0], 6, (name or "")[:24], border=1)
                pdf.cell(widths[1], 6, (c.company or "")[:24], border=1)
                pdf.cell(widths[2], 6, (c.address or "")[:32], border=1)
                pdf.cell(widths[3], 6, (c.city or "")[:14], border=1)
                pdf.cell(widths[4], 6, (c.state or "")[:4], border=1)
                pdf.cell(widths[5], 6, (c.zip_code or "")[:10], border=1)
                pdf.cell(widths[6], 6, (getattr(p, "phone_number", "") or "")[:18], border=1)
                pdf.ln(6)
        else:
            pdf.cell(widths[0], 6, (name or "")[:24], border=1)
            pdf.cell(widths[1], 6, (c.company or "")[:24], border=1)
            pdf.cell(widths[2], 6, (c.address or "")[:32], border=1)
            pdf.cell(widths[3], 6, (c.city or "")[:14], border=1)
            pdf.cell(widths[4], 6, (c.state or "")[:4], border=1)
            pdf.cell(widths[5], 6, (c.zip_code or "")[:10], border=1)
            pdf.cell(widths[6], 6, "", border=1)
            pdf.ln(6)

    logger.info("pdf_phone_book_addr_done", pages=pdf.page_no())
    return _output_pdf_bytes(pdf)


def build_rolodex_info_pdf(clients: List[Client]) -> bytes:
    """Build a Rolodex Info PDF with stacked info blocks per client."""
    logger.info("pdf_rolodex_info_start", count=len(clients))

    pdf = SimplePDF(title="Rolodex Info")
    pdf.add_page()
    pdf.set_font("helvetica", size=11)

    for idx, c in enumerate(clients):
        # Section header
        pdf.set_font("helvetica", "B", 12)
        pdf.cell(0, 7, _format_client_name(c) or "(No Name)", ln=1)
        pdf.set_font("helvetica", size=10)

        # Company
        if c.company:
            pdf.cell(0, 6, f"Company: {c.company}", ln=1)

        # Address lines
        if c.address:
            pdf.cell(0, 6, f"Address: {c.address}", ln=1)
        city_state_zip = _format_city_state_zip(c)
        if city_state_zip:
            pdf.cell(0, 6, f"City/State/ZIP: {city_state_zip}", ln=1)

        # Legacy Id
        if c.rolodex_id:
            pdf.cell(0, 6, f"Legacy ID: {c.rolodex_id}", ln=1)

        # Phones
        phones = getattr(c, "phones", None) or []  # type: ignore[attr-defined]
        if phones:
            for p in phones:
                ptype = (getattr(p, "phone_type", "") or "").strip()
                pnum = (getattr(p, "phone_number", "") or "").strip()
                label = f"{ptype}: {pnum}" if ptype else pnum
                pdf.cell(0, 6, f"Phone: {label}", ln=1)

        # Divider
        pdf.ln(2)
        pdf.set_draw_color(200)
        x1 = pdf.l_margin
        x2 = pdf.w - pdf.r_margin
        y = pdf.get_y()
        pdf.line(x1, y, x2, y)
        pdf.ln(3)

    logger.info("pdf_rolodex_info_done", pages=pdf.page_no())
    return _output_pdf_bytes(pdf)

