"""Packaging generation API routes — persisted in PostgreSQL."""
from __future__ import annotations

import json
from datetime import datetime, timezone
from typing import Any, Literal
from uuid import uuid4

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

from app.db import execute, fetch_all, fetch_one
from app.packaging.export import svg_to_pdf_bytes, svg_to_png_bytes
from app.packaging.generator import FOODLINKK_BRAND, PACKAGING_TYPES, generate_packaging

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


class PackagingSpec(BaseModel):
    type: Literal["folding_box", "wrap", "round_label", "sleeve", "pouch", "tray"] = "folding_box"
    width_mm: float = Field(default=120, gt=0, le=4000)
    height_mm: float = Field(default=80, gt=0, le=4000)
    depth_mm: float = Field(default=40, ge=0, le=4000)
    bleed_mm: float = Field(default=3, ge=0, le=20)
    elements: dict[str, bool] = Field(default_factory=dict)
    brand: dict[str, str] = Field(default_factory=dict)
    text: dict[str, Any] = Field(default_factory=dict)
    barcode_value: str | None = Field(default=None, max_length=64)
    qr_value: str | None = Field(default=None, max_length=512)
    design_name: str | None = Field(default=None, max_length=120)
    project_id: int | None = None
    project_name: str | None = None
    client_id: int | None = None
    created_by: str = "ceo"


class PackagingUpdate(BaseModel):
    spec: PackagingSpec


class CopyToProjectBody(BaseModel):
    target_project_id: int = Field(..., gt=0)
    created_by: str = "ceo"


class PackagingEmailBody(BaseModel):
    to: list[str] = Field(..., min_length=1)
    subject: str = Field(..., min_length=1, max_length=255)
    body: str = ""
    cc: list[str] = Field(default_factory=list)
    attach_formats: list[str] = Field(default_factory=lambda: ["pdf"])
    client_id: int | None = None


def _normalize_spec(spec_data: dict[str, Any]) -> dict[str, Any]:
    if not spec_data.get("brand"):
        spec_data["brand"] = dict(FOODLINKK_BRAND)
    if not spec_data.get("elements"):
        spec_data["elements"] = {
            "barcode": True,
            "logo_area": True,
            "fold_lines": True,
            "cut_lines": True,
            "nutrition_panel": False,
            "ingredients": False,
            "halal_badge": False,
            "window": False,
            "qr_code": False,
            "glue_tabs": False,
            "bleed": True,
            "dimensions": True,
        }
    return spec_data


def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
    out = dict(row)
    spec = out.get("spec") or {}
    if isinstance(spec, str):
        try:
            spec = json.loads(spec)
        except Exception:
            spec = {}
    out["spec"] = spec
    for key in ("created_at",):
        if out.get(key) is not None and hasattr(out[key], "isoformat"):
            out[key] = out[key].isoformat()
    return out


def _get_project(project_id: str) -> dict[str, Any] | None:
    row = fetch_one(
        """
        SELECT po.*, cp.name AS project_name, cp.nas_path, c.name AS client_name, c.id AS client_id
        FROM packaging_outputs po
        LEFT JOIN cockpit_projects cp ON cp.id = po.project_id
        LEFT JOIN clients c ON c.id = cp.client_id
        WHERE po.id = %s
        """,
        (project_id,),
    )
    return _serialize_row(dict(row)) if row else None


def _link_asset(cockpit_project_id: int, packaging_id: str, spec_data: dict[str, Any], created_by: str, file_path: str | None = None) -> None:
    title = spec_data.get("design_name") or spec_data.get("text", {}).get("product_name") or f"Packaging {spec_data.get('type')}"
    existing = fetch_one(
        "SELECT id FROM project_assets WHERE project_id = %s AND asset_type = 'packaging' AND ref_id = %s LIMIT 1",
        (cockpit_project_id, packaging_id),
    )
    payload = json.dumps({"packaging_id": packaging_id, "spec": spec_data})
    if existing:
        execute(
            """
            UPDATE project_assets SET title = %s, payload = %s::jsonb, file_path = COALESCE(%s, file_path)
            WHERE id = %s
            """,
            (title, payload, file_path, existing["id"]),
        )
    else:
        fetch_one(
            """
            INSERT INTO project_assets (project_id, asset_type, ref_id, title, file_path, payload, created_by, source_agent)
            VALUES (%s, 'packaging', %s, %s, %s, %s::jsonb, %s, 'packaging')
            RETURNING id
            """,
            (cockpit_project_id, packaging_id, title, file_path, payload, created_by),
        )
    execute("UPDATE cockpit_projects SET updated_at = NOW() WHERE id = %s", (cockpit_project_id,))


def _resolve_cockpit_project(spec: PackagingSpec) -> int | None:
    cockpit_project_id = spec.project_id
    if spec.project_name and not cockpit_project_id:
        row = fetch_one(
            """
            INSERT INTO cockpit_projects (name, client_id, description, created_by, project_type, updated_at)
            VALUES (%s, %s, %s, %s, 'packaging', NOW())
            RETURNING id
            """,
            (spec.project_name.strip(), spec.client_id, "Packaging project", spec.created_by),
        )
        cockpit_project_id = int(row["id"]) if row else None
    return cockpit_project_id


def _persist_packaging(spec: PackagingSpec, packaging_id: str | None = None) -> dict[str, Any]:
    spec_data = _normalize_spec(spec.model_dump(exclude={"project_id", "project_name", "client_id", "created_by"}))
    svg = generate_packaging(spec_data)
    pid = packaging_id or uuid4().hex
    cockpit_project_id = _resolve_cockpit_project(spec)

    existing = _get_project(pid) if packaging_id else None
    if existing:
        fetch_one(
            """
            UPDATE packaging_outputs SET project_id = %s, spec = %s::jsonb, svg = %s, created_by = %s
            WHERE id = %s RETURNING id
            """,
            (cockpit_project_id, json.dumps(spec_data), svg, spec.created_by, pid),
        )
    else:
        fetch_one(
            """
            INSERT INTO packaging_outputs (id, project_id, spec, svg, created_by, created_at)
            VALUES (%s, %s, %s::jsonb, %s, %s, NOW())
            RETURNING id
            """,
            (pid, cockpit_project_id, json.dumps(spec_data), svg, spec.created_by),
        )

    if cockpit_project_id:
        _link_asset(cockpit_project_id, pid, spec_data, spec.created_by)

    return {
        "id": pid,
        "cockpit_project_id": cockpit_project_id,
        "created_at": datetime.now(timezone.utc).isoformat(),
        "svg": svg,
        "spec": spec_data,
    }


@router.get("/types")
def packaging_types() -> dict[str, Any]:
    labels = {
        "folding_box": "Folding box (sluitdoos)",
        "wrap": "Wrap / banderole",
        "round_label": "Rond label",
        "sleeve": "Sleeve / huls",
        "pouch": "Pouch / zak",
        "tray": "Tray / schaal",
    }
    return {"items": [{"id": t, "label": labels.get(t, t)} for t in PACKAGING_TYPES]}


@router.post("/preview")
def packaging_preview(spec: PackagingSpec) -> dict[str, Any]:
    spec_data = _normalize_spec(spec.model_dump(exclude={"project_id", "project_name", "client_id", "created_by"}))
    return {"svg": generate_packaging(spec_data), "spec": spec_data}


@router.post("/generate")
def packaging_generate(spec: PackagingSpec) -> dict[str, Any]:
    return _persist_packaging(spec)


@router.get("/projects")
def packaging_projects(limit: int = Query(default=30, ge=1, le=200)) -> dict[str, Any]:
    rows = fetch_all(
        """
        SELECT po.id, po.project_id AS cockpit_project_id, po.spec, po.created_by, po.created_at,
               cp.name AS project_name, cp.nas_path, c.name AS client_name
        FROM packaging_outputs po
        LEFT JOIN cockpit_projects cp ON cp.id = po.project_id
        LEFT JOIN clients c ON c.id = cp.client_id
        ORDER BY po.created_at DESC
        LIMIT %s
        """,
        (limit,),
    )
    return {"items": [_serialize_row(dict(r)) for r in rows], "count": len(rows)}


@router.get("/download/{project_id}")
def packaging_download(project_id: str, format: str = Query(default="svg", pattern="^(svg|png|pdf)$")):
    project = _get_project(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Packaging project not found")

    svg = project["svg"]
    slug = (project.get("spec") or {}).get("design_name") or project_id[:8]
    safe_slug = "".join(ch if ch.isalnum() or ch in "-_" else "-" for ch in slug.lower())[:40]
    filename = f"foodlinkk-{safe_slug}.{format}"
    if format == "svg":
        return Response(
            content=svg.encode("utf-8"),
            media_type="image/svg+xml",
            headers={"Content-Disposition": f'attachment; filename="{filename}"'},
        )
    if format == "png":
        return Response(
            content=svg_to_png_bytes(svg),
            media_type="image/png",
            headers={"Content-Disposition": f'attachment; filename="{filename}"'},
        )
    return Response(
        content=svg_to_pdf_bytes(svg),
        media_type="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )


@router.get("/{packaging_id}")
def packaging_get(packaging_id: str) -> dict[str, Any]:
    row = _get_project(packaging_id)
    if not row:
        raise HTTPException(status_code=404, detail="Packaging design not found")
    return {"item": row}


@router.patch("/{packaging_id}")
def packaging_update(packaging_id: str, body: PackagingUpdate) -> dict[str, Any]:
    if not _get_project(packaging_id):
        raise HTTPException(status_code=404, detail="Packaging design not found")
    existing = _get_project(packaging_id) or {}
    spec = body.spec
    if not spec.project_id and existing.get("project_id"):
        spec.project_id = int(existing["project_id"])
    result = _persist_packaging(spec, packaging_id=packaging_id)
    return result


@router.post("/{packaging_id}/duplicate")
def packaging_duplicate(packaging_id: str, created_by: str = Query(default="ceo")) -> dict[str, Any]:
    row = _get_project(packaging_id)
    if not row:
        raise HTTPException(status_code=404, detail="Packaging design not found")
    spec = dict(row.get("spec") or {})
    spec["design_name"] = (spec.get("design_name") or spec.get("text", {}).get("product_name") or "Design") + " (kopie)"
    payload = PackagingSpec(**{**spec, "project_id": int(row["project_id"]) if row.get("project_id") else None, "created_by": created_by})
    return _persist_packaging(payload)


@router.post("/{packaging_id}/copy-to-project")
def packaging_copy_to_project(packaging_id: str, body: CopyToProjectBody) -> dict[str, Any]:
    row = _get_project(packaging_id)
    if not row:
        raise HTTPException(status_code=404, detail="Packaging design not found")
    target = fetch_one("SELECT id, name FROM cockpit_projects WHERE id = %s", (body.target_project_id,))
    if not target:
        raise HTTPException(status_code=404, detail="Target project not found")
    spec = dict(row.get("spec") or {})
    spec["design_name"] = (spec.get("design_name") or "Design") + f" → {target['name']}"
    payload = PackagingSpec(**{**spec, "project_id": body.target_project_id, "created_by": body.created_by})
    return _persist_packaging(payload)


@router.delete("/{packaging_id}")
def packaging_delete(packaging_id: str) -> dict[str, Any]:
    if not _get_project(packaging_id):
        raise HTTPException(status_code=404, detail="Packaging design not found")
    execute("DELETE FROM project_assets WHERE asset_type = 'packaging' AND ref_id = %s", (packaging_id,))
    execute("DELETE FROM packaging_outputs WHERE id = %s", (packaging_id,))
    return {"ok": True, "id": packaging_id}
