"""打印与模板（F11–F13、F18–F19、F23–F25、F49）。"""
import io
import uuid
from urllib.parse import quote

import nh3
from fastapi import APIRouter, Depends, HTTPException, Response
from fastapi.responses import HTMLResponse
from openpyxl import Workbook
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.orm import Session

from flyway.api.deps import current_actor, get_db, require_admin_actor
from flyway.domain.actor import Actor
from flyway.domain.clock import nz_today
from flyway.models import PackingSlip, Template, Transfer
from flyway.printing.fill import fill, slip_data, transfer_data
from flyway.printing.pages import attachment_block, attachment_page, slip_product_list, table_page
from flyway.printing.pdf import A4_LANDSCAPE, render_pdf
from flyway.services.audit import record

router = APIRouter(prefix="/api/print")
admin_router = APIRouter(prefix="/api/templates", dependencies=[Depends(require_admin_actor)])

TEMPLATE_TYPES = {
    "LoadInPrint": ["LoadInPrintHeader", "LoadInPrintCheckSheetBody", "LoadInPrintFooter"],
    "LoadOutPrint": ["LoadOutPrintHeader", "LoadOutPrintCheckSheetBody", "LoadOutPrintFooter"],
    "PackingSlip": ["PackingSlipPrintHeader", "PackingSlipPrintBody", "PackingSlipPrintCommonBlock1",
                    "PackingSlipPrintCommonBlock2", "PackingSlipPrintFooter"],
}
PAGE_BREAK = "<div style='page-break-after: always;'><span style='display: none;'>&nbsp;</span></div>"

# 模板 HTML 白名单（文档第 11 节第 7 项：存储型 XSS）
ALLOWED_TAGS = {"a", "b", "br", "caption", "col", "colgroup", "div", "em", "font", "h1", "h2", "h3", "h4", "h5",
                "h6", "hr", "i", "img", "li", "ol", "p", "s", "small", "span", "strong", "sub", "sup", "table",
                "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul"}
ALLOWED_ATTRS = {"*": {"style", "class", "align", "valign", "width", "height", "border", "cellpadding",
                       "cellspacing", "colspan", "rowspan", "color", "size", "face", "bgcolor"},
                 "a": {"href"}, "img": {"src", "alt"}}


def sanitize(html: str) -> str:
    return nh3.clean(html or "", tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, url_schemes={"http", "https", "data"},
                     link_rel=None)


def template_content(db: Session, type_: str) -> str:
    return db.scalar(select(Template.content).where(Template.type == type_).limit(1)) or ""


def _transfer(db: Session, transfer_id: uuid.UUID) -> Transfer:
    t = db.get(Transfer, transfer_id)
    if t is None or t.is_local:
        raise HTTPException(status_code=404)
    return t


def _slip(db: Session, slip_id: uuid.UUID) -> PackingSlip:
    s = db.get(PackingSlip, slip_id)
    if s is None:
        raise HTTPException(status_code=404)
    return s


def _file(content: bytes, media: str, filename: str, inline: bool = True) -> Response:
    disposition = "inline" if inline else "attachment"
    return Response(content, media_type=media,
                    headers={"Content-Disposition": f"{disposition}; filename*=UTF-8''{quote(filename)}"})


def check_sheet_pdf(db: Session, t: Transfer) -> bytes:
    prefix = "LoadOutPrint" if t.is_load_out else "LoadInPrint"
    data = transfer_data(t)
    body = fill(template_content(db, f"{prefix}CheckSheetBody"), data) + PAGE_BREAK + attachment_block(t)
    return render_pdf(fill(template_content(db, f"{prefix}Header"), data),
                      fill(template_content(db, f"{prefix}Footer"), data), body)


def packing_slip_pdf(db: Session, s: PackingSlip) -> bytes:
    data = {**slip_data(s), "ProductList": slip_product_list(s)}
    t = {k: fill(template_content(db, f"PackingSlipPrint{k}"), data)
         for k in ("Header", "Footer", "Body", "CommonBlock1", "CommonBlock2")}
    return render_pdf(t["Header"], t["Footer"], t["Body"], [t["CommonBlock1"], t["CommonBlock2"]], A4_LANDSCAPE)


def statistics_xlsx(s: PackingSlip) -> bytes:
    """装箱单产品统计（ProductStatisticsExcel.ascx）：每产品一块，每行 5 块。"""
    items = [i for t in s.transfers for i in t.items]
    products: dict[str, dict[str, list]] = {}
    for i in sorted(items, key=lambda i: i.batch.product.title):
        products.setdefault(i.batch.product.title, {}).setdefault(i.batch.number, []).append(i)
    max_rows = max((len(b) for b in products.values()), default=1)
    wb = Workbook()
    sheet = wb.active
    sheet.title = "Product statistics"
    for n, (title, batches) in enumerate(products.items()):
        row = (n // 5) * (max_rows + 2) + 1
        col = (n % 5) * 4 + 1
        sheet.cell(row, col, title)
        sheet.cell(row, col + 1, "Batch code")
        sheet.cell(row, col + 2, "Quantity")
        for j, (number, rows) in enumerate(sorted(batches.items()), start=1):
            sheet.cell(row + j, col + 1, number)
            sheet.cell(row + j, col + 2, f"{sum(i.quantity for i in rows)} ({sum(i.quantity * i.unit for i in rows)})")
    buf = io.BytesIO()
    wb.save(buf)
    return buf.getvalue()


@router.get("/transfers/{transfer_id}/check-sheet.pdf")
def check_sheet(transfer_id: uuid.UUID, db: Session = Depends(get_db)):
    t = _transfer(db, transfer_id)
    return _file(check_sheet_pdf(db, t), "application/pdf", f"{t.code} check sheet.pdf")


@router.get("/transfers/{transfer_id}/table", response_class=HTMLResponse)
def table(transfer_id: uuid.UUID, db: Session = Depends(get_db)):
    return table_page(_transfer(db, transfer_id))


@router.get("/transfers/{transfer_id}/attachment", response_class=HTMLResponse)
def attachment(transfer_id: uuid.UUID, db: Session = Depends(get_db)):
    return attachment_page(_transfer(db, transfer_id))


@router.get("/packing-slips/{slip_id}.pdf")
def packing_slip(slip_id: uuid.UUID, db: Session = Depends(get_db)):
    s = _slip(db, slip_id)
    return _file(packing_slip_pdf(db, s), "application/pdf", f"Packing slip {s.reference}.pdf")


@router.get("/packing-slips/{slip_id}/statistics.xlsx")
def statistics(slip_id: uuid.UUID, db: Session = Depends(get_db)):
    s = _slip(db, slip_id)
    name = f"Packing_slip_{s.reference}_product_statistics_{nz_today():%d-%m-%Y}.xlsx"
    return _file(statistics_xlsx(s), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", name,
                 inline=False)


class TemplateIn(BaseModel):
    content: str


@admin_router.get("")
def list_templates(db: Session = Depends(get_db)):
    """已知类型（打印菜单用）+ 库里其它类型（通用 Edit?type=）。"""
    stored = {t.type: sanitize(t.content or "") for t in db.scalars(select(Template))}
    types = [t for group in TEMPLATE_TYPES.values() for t in group]
    types += sorted(t for t in stored if t not in types)
    return {"groups": TEMPLATE_TYPES, "items": [{"type": t, "content": stored.get(t, "")} for t in types]}


@admin_router.put("/{type_}")
def save_template(type_: str, body: TemplateIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    if not type_.strip() or len(type_) > 50:
        raise HTTPException(status_code=422, detail="Invalid template type.")
    content = sanitize(body.content)
    t = db.scalar(select(Template).where(Template.type == type_).limit(1))
    if t is None:
        t = Template(id=uuid.uuid4(), type=type_)
        db.add(t)
    t.content = content
    db.flush()
    record(db, actor, "edit", "template", t.id, type_)
    db.commit()
    return {"type": type_, "content": content}
