from __future__ import annotations

from io import BytesIO
from math import ceil

from app.models.mix_calculator import MixCalculatorSession


class MixCalculatorPdfUnavailableError(RuntimeError):
    pass


def _fmt_number(value: float, digits: int = 2) -> str:
    return f"{value:.{digits}f}"


def _fractional_bag_warning(session_record: MixCalculatorSession) -> str | None:
    rounded_bags = round(session_record.total_bags)
    if abs(session_record.total_bags - rounded_bags) < 1e-9:
        return None
    return (
        f"Batch size {session_record.batch_size_kg:g}kg produces {session_record.total_bags:.2f} bags "
        f"for {session_record.product_unit_of_measure}. This is not a whole-bag quantity."
    )


def build_mix_calculator_pdf(session_record: MixCalculatorSession) -> bytes:
    try:
        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
        from reportlab.lib.units import mm
        from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
    except ModuleNotFoundError as exc:
        raise MixCalculatorPdfUnavailableError(
            "PDF generation is unavailable because 'reportlab' is not installed. "
            "Install backend dependencies again to enable PDF export."
        ) from exc

    buffer = BytesIO()
    document = SimpleDocTemplate(
        buffer,
        pagesize=A4,
        leftMargin=14 * mm,
        rightMargin=14 * mm,
        topMargin=14 * mm,
        bottomMargin=14 * mm,
        title=f"{session_record.session_number} - {session_record.product_name}",
        author="Lean 101 Clients",
    )

    styles = getSampleStyleSheet()
    eyebrow = ParagraphStyle(
        "Eyebrow",
        parent=styles["BodyText"],
        fontName="Helvetica-Bold",
        fontSize=8,
        leading=10,
        textColor=colors.HexColor("#62736B"),
        spaceAfter=5,
    )
    title = ParagraphStyle(
        "Title",
        parent=styles["Heading1"],
        fontName="Helvetica-Bold",
        fontSize=24,
        leading=26,
        textColor=colors.HexColor("#21312A"),
        spaceAfter=6,
    )
    subtitle = ParagraphStyle(
        "Subtitle",
        parent=styles["BodyText"],
        fontName="Helvetica",
        fontSize=10,
        leading=13,
        textColor=colors.HexColor("#6B7A73"),
    )
    label = ParagraphStyle(
        "Label",
        parent=styles["BodyText"],
        fontName="Helvetica-Bold",
        fontSize=7,
        leading=9,
        textColor=colors.HexColor("#6B7A73"),
    )
    value = ParagraphStyle(
        "Value",
        parent=styles["BodyText"],
        fontName="Helvetica-Bold",
        fontSize=11,
        leading=13,
        textColor=colors.HexColor("#21312A"),
    )
    card_value = ParagraphStyle(
        "CardValue",
        parent=value,
        fontSize=16,
        leading=18,
    )
    body = ParagraphStyle(
        "Body",
        parent=styles["BodyText"],
        fontName="Helvetica",
        fontSize=9,
        leading=12,
        textColor=colors.HexColor("#304038"),
    )
    section_title = ParagraphStyle(
        "SectionTitle",
        parent=styles["Heading2"],
        fontName="Helvetica-Bold",
        fontSize=13,
        leading=15,
        textColor=colors.HexColor("#21312A"),
    )

    warnings = []
    bag_warning = _fractional_bag_warning(session_record)
    if bag_warning:
        warnings.append(bag_warning)

    story = [
        Paragraph(f"Mix Calculator  |  {session_record.session_number}", eyebrow),
        Paragraph(session_record.product_name, title),
        Paragraph(f"{session_record.client_name} &nbsp;&middot;&nbsp; {session_record.mix_name}", subtitle),
        Spacer(1, 8),
    ]

    header_table = Table(
        [
            [
                [
                    Paragraph("Mix date", label),
                    Paragraph(session_record.mix_date.strftime("%d %b %Y"), value),
                ],
                [
                    Paragraph("Prepared by", label),
                    Paragraph(session_record.prepared_by_name, value),
                ],
                [
                    Paragraph("Status", label),
                    Paragraph(session_record.status.title(), value),
                ],
            ]
        ],
        colWidths=[60 * mm, 60 * mm, 52 * mm],
    )
    header_table.setStyle(
        TableStyle(
            [
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("BOX", (0, 0), (-1, -1), 0.8, colors.HexColor("#DBE4DE")),
                ("INNERGRID", (0, 0), (-1, -1), 0.8, colors.HexColor("#DBE4DE")),
                ("BACKGROUND", (0, 0), (-1, -1), colors.white),
                ("LEFTPADDING", (0, 0), (-1, -1), 10),
                ("RIGHTPADDING", (0, 0), (-1, -1), 10),
                ("TOPPADDING", (0, 0), (-1, -1), 9),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 9),
            ]
        )
    )
    story.extend([header_table, Spacer(1, 10)])

    summary_table = Table(
        [
            [
                [Paragraph("Batch size", label), Paragraph(f"{_fmt_number(session_record.batch_size_kg)}kg", card_value)],
                [Paragraph("Total output", label), Paragraph(f"{_fmt_number(session_record.total_kg)}kg", card_value)],
                [Paragraph("Bags", label), Paragraph(_fmt_number(session_record.total_bags), card_value)],
                [Paragraph("Unit pack", label), Paragraph(f"{_fmt_number(session_record.product_unit_size_kg)}kg", card_value)],
            ]
        ],
        colWidths=[43 * mm, 43 * mm, 43 * mm, 43 * mm],
    )
    summary_table.setStyle(
        TableStyle(
            [
                ("BOX", (0, 0), (-1, -1), 0.8, colors.HexColor("#DBE4DE")),
                ("INNERGRID", (0, 0), (-1, -1), 0.8, colors.HexColor("#DBE4DE")),
                ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#F9FBFA")),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("LEFTPADDING", (0, 0), (-1, -1), 10),
                ("RIGHTPADDING", (0, 0), (-1, -1), 10),
                ("TOPPADDING", (0, 0), (-1, -1), 10),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 10),
            ]
        )
    )
    story.extend([summary_table, Spacer(1, 10)])

    detail_table = Table(
        [
            [
                [Paragraph("Mix source", label), Paragraph(session_record.mix_name, value), Paragraph(f"Saved against {session_record.product_unit_of_measure} units.", body)],
                [Paragraph("Composition", label), Paragraph(f"{_fmt_number(sum(line.mix_percentage for line in session_record.lines))}%", value), Paragraph(f"{len(session_record.lines)} raw material{'s' if len(session_record.lines) != 1 else ''} in the blend.", body)],
                [Paragraph("Estimated pages", label), Paragraph(str(max(1, ceil(len(session_record.lines) / 18))), value), Paragraph("Formatted for A4 PDF export.", body)],
            ]
        ],
        colWidths=[60 * mm, 60 * mm, 52 * mm],
    )
    detail_table.setStyle(
        TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#F4F8F5")),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("LEFTPADDING", (0, 0), (-1, -1), 10),
                ("RIGHTPADDING", (0, 0), (-1, -1), 10),
                ("TOPPADDING", (0, 0), (-1, -1), 10),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 10),
            ]
        )
    )
    story.extend([detail_table, Spacer(1, 10)])

    if session_record.notes:
        notes_table = Table(
            [[Paragraph("Notes", label)], [Paragraph(session_record.notes.replace("\n", "<br/>"), body)]],
            colWidths=[172 * mm],
        )
        notes_table.setStyle(
            TableStyle(
                [
                    ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#F4F8F5")),
                    ("BOX", (0, 0), (-1, -1), 0.8, colors.HexColor("#DBE4DE")),
                    ("LEFTPADDING", (0, 0), (-1, -1), 10),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 10),
                    ("TOPPADDING", (0, 0), (-1, -1), 8),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
                ]
            )
        )
        story.extend([notes_table, Spacer(1, 10)])

    if warnings:
        warning_rows = [[Paragraph("Warnings", label)]]
        warning_rows.extend([[Paragraph(warning, body)] for warning in warnings])
        warnings_table = Table(warning_rows, colWidths=[172 * mm])
        warnings_table.setStyle(
            TableStyle(
                [
                    ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#FFF5E6")),
                    ("BACKGROUND", (0, 1), (-1, -1), colors.HexColor("#FFF9EF")),
                    ("BOX", (0, 0), (-1, -1), 0.8, colors.HexColor("#E8C483")),
                    ("LEFTPADDING", (0, 0), (-1, -1), 10),
                    ("RIGHTPADDING", (0, 0), (-1, -1), 10),
                    ("TOPPADDING", (0, 0), (-1, -1), 8),
                    ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
                ]
            )
        )
        story.extend([warnings_table, Spacer(1, 10)])

    story.extend(
        [
            Paragraph("Required Raw Materials", label),
            Paragraph("Blend composition", section_title),
            Paragraph(f"{session_record.product_unit_of_measure} · {_fmt_number(session_record.product_unit_size_kg)}kg per unit", subtitle),
            Spacer(1, 6),
        ]
    )

    table_rows = [["Raw material", "Mix %", "Required kg", "Unit"]]
    for line in session_record.lines:
        table_rows.append(
            [
                Paragraph(f"<b>{line.raw_material_name}</b>", body),
                Paragraph(f"{_fmt_number(line.mix_percentage)}%", body),
                Paragraph(f"{_fmt_number(line.required_kg)}kg", body),
                Paragraph(line.unit, body),
            ]
        )

    composition_table = Table(table_rows, colWidths=[88 * mm, 24 * mm, 34 * mm, 26 * mm], repeatRows=1)
    composition_table.setStyle(
        TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#EEF4F0")),
                ("TEXTCOLOR", (0, 0), (-1, 0), colors.HexColor("#4F6158")),
                ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
                ("FONTSIZE", (0, 0), (-1, 0), 8),
                ("BOTTOMPADDING", (0, 0), (-1, 0), 8),
                ("TOPPADDING", (0, 0), (-1, 0), 8),
                ("LEFTPADDING", (0, 0), (-1, -1), 9),
                ("RIGHTPADDING", (0, 0), (-1, -1), 9),
                ("GRID", (0, 0), (-1, -1), 0.6, colors.HexColor("#DBE4DE")),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#FBFCFB")]),
            ]
        )
    )
    story.append(composition_table)

    document.build(story)
    return buffer.getvalue()
