"""Export endpoints — DOCX + PDF zip paketleri.

EPATS (TPMK elektronik başvuru) kuralı: her unsur ayrı dosya. Tek ZIP içinde
3 (opsiyonel 4) ayrı dosya döner.

- POST /api/v1/export/docx — 3-4 DOCX, format="docx"
- POST /api/v1/export/pdf  — 3-4 PDF (LibreOffice headless ile DOCX'ten çevrilir)
- POST /api/v1/export/both — 3-4 DOCX + 3-4 PDF (ikisi de dahil)
"""

from __future__ import annotations

import asyncio
import base64
import binascii
import io
import re
import zipfile
from typing import Literal

from fastapi import APIRouter, HTTPException
from fastapi.responses import Response
from pydantic import BaseModel, Field

from app.services.docx import (
    LibreOfficeNotFoundError,
    PdfConversionError,
    build_abstract_docx,
    build_claims_docx,
    build_description_docx,
    build_figures_docx,
    convert_docx_to_pdf,
)

router = APIRouter(prefix="/export", tags=["export"])


class ExportClaim(BaseModel):
    number: int = Field(..., ge=1, le=200)
    type: Literal["independent", "dependent"]
    parent: int | None = None
    text: str = Field(..., min_length=5)


class ExportFigure(BaseModel):
    png_base64: str = Field(..., min_length=50, description="Base64-encoded PNG bytes")
    title: str | None = Field(default=None, max_length=200)


class ExportRequest(BaseModel):
    title: str = Field(..., min_length=3, max_length=200)
    reference_code: str | None = Field(default=None, max_length=50)
    description_markdown: str = Field(..., min_length=100)
    claims: list[ExportClaim] = Field(..., min_length=1)
    abstract: str = Field(..., min_length=10)
    figures: list[ExportFigure] = Field(default_factory=list, max_length=50)


def _slug(value: str) -> str:
    s = re.sub(r"[^a-zA-Z0-9_-]+", "_", value).strip("_")
    return s[:60] or "patent"


def _build_all_docx(payload: ExportRequest) -> dict[str, bytes]:
    """Tüm DOCX dosyalarını üret, {filename_base: bytes} dict'i döner.

    Figures boşsa Resimler.docx yoktur.
    """
    docx_files: dict[str, bytes] = {
        "Tarifname": build_description_docx(
            title=payload.title, markdown=payload.description_markdown
        ),
        "Istemler": build_claims_docx(
            title=payload.title, claims=[c.model_dump() for c in payload.claims]
        ),
        "Ozet": build_abstract_docx(title=payload.title, abstract=payload.abstract),
    }

    if payload.figures:
        decoded: list[dict] = []
        for idx, fig in enumerate(payload.figures, start=1):
            try:
                png = base64.b64decode(fig.png_base64, validate=True)
            except (binascii.Error, ValueError) as exc:
                raise HTTPException(
                    status_code=400,
                    detail=f"Şekil {idx} base64 decode edilemedi: {exc}",
                ) from exc
            if not png.startswith(b"\x89PNG\r\n\x1a\n"):
                raise HTTPException(
                    status_code=400,
                    detail=f"Şekil {idx} geçerli PNG değil (magic bytes yok).",
                )
            decoded.append({"png_bytes": png, "title": fig.title})
        docx_files["Resimler"] = build_figures_docx(title=payload.title, figures=decoded)

    return docx_files


async def _convert_all_to_pdf(
    docx_files: dict[str, bytes],
) -> dict[str, bytes]:
    """Tüm DOCX'leri paralel PDF'e çevir."""
    tasks = [convert_docx_to_pdf(b) for b in docx_files.values()]
    try:
        pdf_bytes_list = await asyncio.gather(*tasks)
    except LibreOfficeNotFoundError as exc:
        raise HTTPException(status_code=503, detail=str(exc)) from exc
    except PdfConversionError as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc

    return dict(zip(docx_files.keys(), pdf_bytes_list, strict=True))


def _build_zip(
    *,
    base: str,
    docx_files: dict[str, bytes] | None,
    pdf_files: dict[str, bytes] | None,
) -> bytes:
    """ZIP paketi — her katman opsiyonel."""
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        if docx_files:
            for name, data in docx_files.items():
                zf.writestr(f"{base}_{name}.docx", data)
        if pdf_files:
            for name, data in pdf_files.items():
                zf.writestr(f"{base}_{name}.pdf", data)
    return buf.getvalue()


@router.post("/docx")
async def export_docx(payload: ExportRequest) -> Response:
    """Yalnızca DOCX ZIP paketi."""
    base = _slug(payload.reference_code or payload.title)
    docx_files = _build_all_docx(payload)

    return Response(
        content=_build_zip(base=base, docx_files=docx_files, pdf_files=None),
        media_type="application/zip",
        headers={
            "Content-Disposition": f'attachment; filename="{base}_TurkPatent.zip"',
        },
    )


@router.post("/pdf")
async def export_pdf(payload: ExportRequest) -> Response:
    """Yalnızca PDF ZIP paketi (DOCX'lerden LibreOffice ile çevrilir)."""
    base = _slug(payload.reference_code or payload.title)
    docx_files = _build_all_docx(payload)
    pdf_files = await _convert_all_to_pdf(docx_files)

    return Response(
        content=_build_zip(base=base, docx_files=None, pdf_files=pdf_files),
        media_type="application/zip",
        headers={
            "Content-Disposition": f'attachment; filename="{base}_TurkPatent_PDF.zip"',
        },
    )


@router.post("/both")
async def export_both(payload: ExportRequest) -> Response:
    """Hem DOCX hem PDF — tek ZIP içinde 6-8 dosya."""
    base = _slug(payload.reference_code or payload.title)
    docx_files = _build_all_docx(payload)
    pdf_files = await _convert_all_to_pdf(docx_files)

    return Response(
        content=_build_zip(base=base, docx_files=docx_files, pdf_files=pdf_files),
        media_type="application/zip",
        headers={
            "Content-Disposition": f'attachment; filename="{base}_TurkPatent_Full.zip"',
        },
    )
