"""Dependency-free report exporters.

The project venv is offline and has no openpyxl / reportlab, so:
  - CSV  -> stdlib csv module
  - XLSX -> a minimal but valid Office Open XML package built with zipfile
  - PDF  -> a minimal hand-assembled PDF (Courier, landscape A4, text table)

Each function takes `headers` (list[str]) and `rows` (list[list]) and
returns bytes ready to stream back with the right content type.
"""

import csv
import io
import zipfile
from xml.sax.saxutils import escape


def to_csv(headers, rows):
    buf = io.StringIO()
    writer = csv.writer(buf)
    writer.writerow(headers)
    writer.writerows(rows)
    # utf-8-sig so Excel detects the encoding when double-clicked
    return buf.getvalue().encode("utf-8-sig")


# ─── XLSX ─────────────────────────────────────────────────────────────────────

_XLSX_CONTENT_TYPES = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
</Types>"""

_XLSX_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>"""

_XLSX_WORKBOOK = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets><sheet name="Report" sheetId="1" r:id="rId1"/></sheets>
</workbook>"""

_XLSX_WORKBOOK_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
</Relationships>"""


def _xlsx_cell(value):
    if value is None or value == "":
        return "<c/>"
    if isinstance(value, (int, float)):
        return f"<c><v>{value}</v></c>"
    return f'<c t="inlineStr"><is><t>{escape(str(value))}</t></is></c>'


def to_xlsx(headers, rows):
    sheet_rows = []
    for row in [headers] + list(rows):
        cells = "".join(_xlsx_cell(v) for v in row)
        sheet_rows.append(f"<row>{cells}</row>")
    sheet = (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
        '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
        f"<sheetData>{''.join(sheet_rows)}</sheetData></worksheet>"
    )

    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("[Content_Types].xml", _XLSX_CONTENT_TYPES)
        zf.writestr("_rels/.rels", _XLSX_RELS)
        zf.writestr("xl/workbook.xml", _XLSX_WORKBOOK)
        zf.writestr("xl/_rels/workbook.xml.rels", _XLSX_WORKBOOK_RELS)
        zf.writestr("xl/worksheets/sheet1.xml", sheet)
    return buf.getvalue()


# ─── PDF ──────────────────────────────────────────────────────────────────────

# Landscape A4 in points; Courier so plain-text column padding lines up.
_PAGE_W, _PAGE_H = 842, 595
_MARGIN = 30
_FONT_SIZE = 8
_LINE_H = 12
_ROWS_PER_PAGE = (_PAGE_H - 2 * _MARGIN) // _LINE_H - 2  # minus title + header lines
_MAX_CHARS = int((_PAGE_W - 2 * _MARGIN) / (_FONT_SIZE * 0.6))  # Courier glyph width


def _pdf_escape(text):
    return (
        str(text)
        .replace("\\", r"\\")
        .replace("(", r"\(")
        .replace(")", r"\)")
        .encode("latin-1", "replace")
        .decode("latin-1")
    )


def _table_lines(headers, rows):
    """Pad each column to its widest value so Courier renders an aligned table."""
    table = [[("" if v is None else str(v)) for v in row] for row in [headers] + list(rows)]
    widths = [max(len(r[i]) for r in table) for i in range(len(headers))]
    # Shrink the widest columns until the line fits the page
    while sum(widths) + 2 * (len(widths) - 1) > _MAX_CHARS and max(widths) > 8:
        widths[widths.index(max(widths))] -= 1
    lines = []
    for row in table:
        cells = []
        for i, v in enumerate(row):
            v = v if len(v) <= widths[i] else v[: max(widths[i] - 1, 1)] + "…"
            cells.append(v.ljust(widths[i]))
        lines.append("  ".join(cells).rstrip())
    return lines[0], lines[1:]


def to_pdf(headers, rows, title="Report"):
    header_line, data_lines = _table_lines(headers, rows)
    pages = [data_lines[i : i + _ROWS_PER_PAGE] for i in range(0, len(data_lines), _ROWS_PER_PAGE)] or [[]]

    # objects: 1 catalog, 2 pages, 3 courier, 4 courier-bold, then page+content pairs
    objects = {}
    page_ids = []
    next_id = 5
    for chunk in pages:
        page_id, content_id = next_id, next_id + 1
        next_id += 2
        page_ids.append(page_id)

        y = _PAGE_H - _MARGIN
        parts = [f"BT /F2 {_FONT_SIZE + 2} Tf {_MARGIN} {y} Td ({_pdf_escape(title)}) Tj"]
        parts.append(f"/F2 {_FONT_SIZE} Tf 0 -{_LINE_H + 4} Td ({_pdf_escape(header_line)}) Tj")
        parts.append(f"/F1 {_FONT_SIZE} Tf")
        for line in chunk:
            parts.append(f"0 -{_LINE_H} Td ({_pdf_escape(line)}) Tj")
        parts.append("ET")
        stream = "\n".join(parts).encode("latin-1")

        objects[page_id] = (
            f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {_PAGE_W} {_PAGE_H}] "
            f"/Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents {content_id} 0 R >>"
        ).encode("latin-1")
        objects[content_id] = (
            f"<< /Length {len(stream)} >>\nstream\n".encode("latin-1") + stream + b"\nendstream"
        )

    kids = " ".join(f"{pid} 0 R" for pid in page_ids)
    objects[1] = b"<< /Type /Catalog /Pages 2 0 R >>"
    objects[2] = f"<< /Type /Pages /Kids [{kids}] /Count {len(page_ids)} >>".encode("latin-1")
    objects[3] = b"<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>"
    objects[4] = b"<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold >>"

    out = io.BytesIO()
    out.write(b"%PDF-1.4\n")
    offsets = {}
    for obj_id in sorted(objects):
        offsets[obj_id] = out.tell()
        out.write(f"{obj_id} 0 obj\n".encode("latin-1"))
        out.write(objects[obj_id])
        out.write(b"\nendobj\n")

    xref_pos = out.tell()
    count = len(objects) + 1
    out.write(f"xref\n0 {count}\n".encode("latin-1"))
    out.write(b"0000000000 65535 f \n")
    for obj_id in sorted(objects):
        out.write(f"{offsets[obj_id]:010d} 00000 n \n".encode("latin-1"))
    out.write(
        f"trailer\n<< /Size {count} /Root 1 0 R >>\nstartxref\n{xref_pos}\n%%EOF".encode("latin-1")
    )
    return out.getvalue()


EXPORT_FORMATS = {
    "csv": (to_csv, "text/csv", "csv"),
    "excel": (to_xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"),
    "pdf": (to_pdf, "application/pdf", "pdf"),
}
