"""SVG packaging generator for Foodlinkk — rich design options."""
from __future__ import annotations

import base64
from io import BytesIO
from typing import Any

import svgwrite
from barcode import Code128
from barcode.writer import SVGWriter

MM_TO_PX = 3.7795275591
DEFAULT_BARCODE_VALUE = "8710000000012"

FOODLINKK_BRAND = {
    "bg": "#0b1220",
    "panel": "#101a2d",
    "primary": "#00e5ff",
    "secondary": "#ffd700",
    "accent": "#b8ff3c",
    "text": "#e2e8f0",
    "muted": "#94a3b8",
    "cut_line": "#ef4444",
    "fold_line": "#60a5fa",
    "bleed": "#f97316",
    "halal": "#22c55e",
}

PACKAGING_TYPES = (
    "folding_box",
    "wrap",
    "round_label",
    "sleeve",
    "pouch",
    "tray",
)


def _mm(mm: float) -> float:
    return round(float(mm) * MM_TO_PX, 2)


def _elements_enabled(elements: Any, name: str) -> bool:
    if isinstance(elements, dict):
        return bool(elements.get(name))
    if isinstance(elements, list):
        return name in elements
    return False


def _barcode_data_uri(value: str) -> str:
    barcode = Code128(value, writer=SVGWriter())
    stream = BytesIO()
    barcode.write(stream)
    encoded = base64.b64encode(stream.getvalue()).decode("ascii")
    return f"data:image/svg+xml;base64,{encoded}"


def _qr_placeholder_svg(size: int = 120) -> str:
    """Simple QR-style grid when qrcode lib unavailable."""
    cell = max(size // 10, 8)
    parts = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" viewBox="0 0 {size} {size}">',
        f'<rect width="{size}" height="{size}" fill="#fff"/>',
    ]
    for y in range(0, size, cell):
        for x in range(0, size, cell):
            if (x + y) % (cell * 2) == 0 or x < cell * 3 and y < cell * 3 or x > size - cell * 4 and y < cell * 3:
                parts.append(f'<rect x="{x}" y="{y}" width="{cell}" height="{cell}" fill="#111"/>')
    parts.append("</svg>")
    raw = "".join(parts).encode("utf-8")
    return f"data:image/svg+xml;base64,{base64.b64encode(raw).decode('ascii')}"


def _spec_text(spec: dict[str, Any]) -> dict[str, str]:
    text = spec.get("text") or {}
    if not isinstance(text, dict):
        text = {}
    brand = {**FOODLINKK_BRAND, **(spec.get("brand") or {})}
    return {
        "product_name": str(text.get("product_name") or brand.get("product_name") or "FOODLINKK"),
        "tagline": str(text.get("tagline") or brand.get("tagline") or "Premium food solutions"),
        "subtitle": str(text.get("subtitle") or text.get("weight") or ""),
        "ingredients": str(text.get("ingredients") or ""),
        "best_before": str(text.get("best_before") or "Ten minste houdbaar tot: zie verpakking"),
        "origin": str(text.get("origin") or "Geproduceerd in NL"),
    }


def _nutrition_rows(spec: dict[str, Any]) -> list[tuple[str, str]]:
    text = spec.get("text") or {}
    rows = text.get("nutrition") if isinstance(text, dict) else None
    if isinstance(rows, list) and rows:
        out: list[tuple[str, str]] = []
        for row in rows[:12]:
            if isinstance(row, dict):
                out.append((str(row.get("k") or row.get("label") or ""), str(row.get("v") or row.get("value") or "")))
        return out
    return [
        ("Energie", "450 kJ / 107 kcal"),
        ("Vetten", "4.2 g"),
        ("waarvan verzadigd", "1.1 g"),
        ("Koolhydraten", "12 g"),
        ("waarvan suikers", "2.8 g"),
        ("Eiwitten", "6.5 g"),
        ("Zout", "0.85 g"),
    ]


def _draw_bleed(dwg: svgwrite.Drawing, x: float, y: float, w: float, h: float, brand: dict[str, str], bleed_mm: float) -> None:
    if bleed_mm <= 0:
        return
    b = _mm(bleed_mm)
    dwg.add(
        dwg.rect(
            insert=(x - b, y - b),
            size=(w + 2 * b, h + 2 * b),
            fill="none",
            stroke=brand["bleed"],
            stroke_dasharray="6,4",
            stroke_width=1.0,
            stroke_opacity=0.7,
        )
    )


def _draw_logo_block(dwg: svgwrite.Drawing, x: float, y: float, w: float, h: float, brand: dict[str, str], txt: dict[str, str]) -> None:
    dwg.add(
        dwg.rect(insert=(x, y), size=(w, h), rx=10, ry=10, fill=brand["panel"], stroke=brand["secondary"], stroke_width=2)
    )
    dwg.add(
        dwg.text(
            txt["product_name"][:42],
            insert=(x + 14, y + h * 0.42),
            fill=brand["text"],
            font_size=min(22, max(12, w * 0.045)),
            font_family="Arial, Helvetica, sans-serif",
            font_weight="bold",
        )
    )
    if txt["tagline"]:
        dwg.add(
            dwg.text(
                txt["tagline"][:60],
                insert=(x + 14, y + h * 0.62),
                fill=brand["primary"],
                font_size=min(14, max(9, w * 0.028)),
                font_family="Arial, Helvetica, sans-serif",
            )
        )
    if txt["subtitle"]:
        dwg.add(
            dwg.text(
                txt["subtitle"][:24],
                insert=(x + 14, y + h * 0.82),
                fill=brand["muted"],
                font_size=11,
                font_family="Arial, Helvetica, sans-serif",
            )
        )


def _draw_nutrition_panel(
    dwg: svgwrite.Drawing, x: float, y: float, w: float, h: float, brand: dict[str, str], rows: list[tuple[str, str]]
) -> None:
    dwg.add(dwg.rect(insert=(x, y), size=(w, h), fill="#ffffff", stroke=brand["text"], stroke_width=1.2, rx=4))
    dwg.add(
        dwg.text(
            "Voedingswaarden per 100g",
            insert=(x + 8, y + 16),
            fill="#111827",
            font_size=11,
            font_weight="bold",
            font_family="Arial, sans-serif",
        )
    )
    line_y = y + 26
    for label, value in rows:
        dwg.add(dwg.text(label[:28], insert=(x + 8, line_y), fill="#374151", font_size=9, font_family="Arial, sans-serif"))
        dwg.add(dwg.text(value[:16], insert=(x + w - 8, line_y), fill="#111827", font_size=9, font_family="Arial, sans-serif", text_anchor="end"))
        line_y += 13
        if line_y > y + h - 6:
            break


def _draw_ingredients(dwg: svgwrite.Drawing, x: float, y: float, w: float, text: str, brand: dict[str, str]) -> None:
    dwg.add(
        dwg.text(
            "Ingrediënten:",
            insert=(x, y),
            fill=brand["text"],
            font_size=10,
            font_weight="bold",
            font_family="Arial, sans-serif",
        )
    )
    chunk = text[:220] or "Ingrediënten volgens recept — vul aan in Packaging Studio."
    words, line, line_y, line_h = chunk.split(), "", y + 14, 12
    for word in words:
        test = (line + " " + word).strip()
        if len(test) > 42:
            dwg.add(dwg.text(line, insert=(x, line_y), fill=brand["muted"], font_size=9, font_family="Arial, sans-serif"))
            line, line_y = word, line_y + line_h
        else:
            line = test
    if line:
        dwg.add(dwg.text(line, insert=(x, line_y), fill=brand["muted"], font_size=9, font_family="Arial, sans-serif"))


def _draw_halal_badge(dwg: svgwrite.Drawing, x: float, y: float, brand: dict[str, str]) -> None:
    r = 28
    dwg.add(dwg.circle(center=(x + r, y + r), r=r, fill=brand["halal"], stroke="#ffffff", stroke_width=2))
    dwg.add(
        dwg.text(
            "HALAL",
            insert=(x + r, y + r + 5),
            fill="#052e16",
            font_size=11,
            font_weight="bold",
            font_family="Arial, sans-serif",
            text_anchor="middle",
        )
    )


def _draw_window(dwg: svgwrite.Drawing, x: float, y: float, w: float, h: float) -> None:
    dwg.add(dwg.rect(insert=(x, y), size=(w, h), fill="#bae6fd", fill_opacity=0.35, stroke="#38bdf8", stroke_width=1.5, rx=6))
    dwg.add(dwg.text("WINDOW", insert=(x + w / 2, y + h / 2 + 4), fill="#0c4a6e", font_size=10, text_anchor="middle", font_family="Arial, sans-serif"))


def _draw_dimensions(dwg: svgwrite.Drawing, x: float, y: float, w: float, h: float, spec: dict[str, Any], brand: dict[str, str]) -> None:
    label = f"{spec.get('width_mm')} × {spec.get('height_mm')} × {spec.get('depth_mm')} mm"
    dwg.add(
        dwg.text(
            label,
            insert=(x + w / 2, y + h + 18),
            fill=brand["muted"],
            font_size=10,
            text_anchor="middle",
            font_family="Arial, sans-serif",
        )
    )


def _canvas_size(ptype: str, width_mm: float, height_mm: float, depth_mm: float) -> tuple[float, float]:
    if ptype == "folding_box":
        return _mm((width_mm * 2) + (depth_mm * 2) + 20), _mm(height_mm + depth_mm + 20)
    if ptype == "wrap":
        return _mm(width_mm + 20), _mm(height_mm + 20)
    if ptype == "round_label":
        d = max(min(width_mm, height_mm), 20)
        return _mm(d + 20), _mm(d + 20)
    if ptype == "sleeve":
        return _mm((width_mm * 2) + depth_mm + 20), _mm(height_mm + 20)
    if ptype == "pouch":
        return _mm(width_mm + 20), _mm(height_mm + depth_mm + 20)
    if ptype == "tray":
        return _mm(width_mm + 20), _mm(height_mm + depth_mm + 20)
    raise ValueError(f"Unsupported packaging type: {ptype}")


def generate_packaging(spec: dict[str, Any]) -> str:
    """Create an SVG packaging design from a rich spec."""
    ptype = (spec.get("type") or "folding_box").strip().lower()
    if ptype not in PACKAGING_TYPES:
        raise ValueError(f"Unsupported packaging type: {ptype}")

    width_mm = float(spec.get("width_mm", 120))
    height_mm = float(spec.get("height_mm", 80))
    depth_mm = float(spec.get("depth_mm", 40))
    bleed_mm = float(spec.get("bleed_mm", 3))
    elements = spec.get("elements", {})
    brand = {**FOODLINKK_BRAND, **(spec.get("brand") or {})}
    txt = _spec_text(spec)

    canvas_w, canvas_h = _canvas_size(ptype, width_mm, height_mm, depth_mm)
    dwg = svgwrite.Drawing(size=(canvas_w, canvas_h))
    dwg.viewbox(0, 0, canvas_w, canvas_h)

    dwg.add(dwg.rect(insert=(0, 0), size=(canvas_w, canvas_h), fill=brand["bg"]))
    dwg.add(
        dwg.rect(
            insert=(4, 4),
            size=(canvas_w - 8, canvas_h - 8),
            fill=brand["panel"],
            rx=10,
            ry=10,
            stroke=brand["primary"],
            stroke_opacity=0.25,
            stroke_width=2,
        )
    )

    margin = 24
    body_w = body_h = x0 = y0 = 0.0

    if ptype == "folding_box":
        body_w = _mm(width_mm)
        body_h = _mm(height_mm)
        depth_w = _mm(depth_mm)
        x0, y0 = margin, margin
        panels = [depth_w, body_w, depth_w, body_w]
        x = x0
        for idx, panel_w in enumerate(panels):
            fill = brand["primary"] if idx % 2 else brand["secondary"]
            dwg.add(dwg.rect(insert=(x, y0), size=(panel_w, body_h), fill="none", stroke=fill, stroke_opacity=0.45, stroke_width=1.6))
            x += panel_w
        if _elements_enabled(elements, "fold_lines"):
            x = x0 + panels[0]
            for panel_w in panels[1:]:
                dwg.add(dwg.line(start=(x, y0), end=(x, y0 + body_h), stroke=brand["fold_line"], stroke_dasharray="8,6", stroke_width=1.2))
                x += panel_w
        if _elements_enabled(elements, "cut_lines"):
            dwg.add(dwg.rect(insert=(x0, y0), size=(sum(panels), body_h), fill="none", stroke=brand["cut_line"], stroke_dasharray="5,4", stroke_width=1.1))
        if _elements_enabled(elements, "glue_tabs"):
            tab_w, tab_h = _mm(12), _mm(8)
            dwg.add(dwg.rect(insert=(x0 - tab_w, y0 + body_h * 0.4), size=(tab_w, tab_h), fill=brand["accent"], fill_opacity=0.35, stroke=brand["accent"]))
        logo_x = x0 + panels[0] + (_mm(width_mm) * 0.1)
        logo_y = y0 + (_mm(height_mm) * 0.12)
        logo_w = _mm(width_mm) * 0.8
        logo_h = _mm(height_mm) * 0.38

    elif ptype == "wrap":
        body_w, body_h = _mm(width_mm), _mm(height_mm)
        x0, y0 = margin, margin
        dwg.add(dwg.rect(insert=(x0, y0), size=(body_w, body_h), fill="none", stroke=brand["primary"], stroke_width=2.2))
        if _elements_enabled(elements, "fold_lines"):
            dwg.add(dwg.line(start=(x0 + body_w / 2, y0), end=(x0 + body_w / 2, y0 + body_h), stroke=brand["fold_line"], stroke_dasharray="8,6", stroke_width=1.2))
        if _elements_enabled(elements, "cut_lines"):
            dwg.add(dwg.rect(insert=(x0, y0), size=(body_w, body_h), fill="none", stroke=brand["cut_line"], stroke_dasharray="6,4", stroke_width=1.1))
        logo_x, logo_y = x0 + body_w * 0.1, y0 + body_h * 0.12
        logo_w, logo_h = body_w * 0.8, body_h * 0.35

    elif ptype == "sleeve":
        body_w, body_h = _mm(width_mm), _mm(height_mm)
        depth_w = _mm(depth_mm)
        x0, y0 = margin, margin
        panels = [body_w, depth_w, body_w]
        x = x0
        for panel_w in panels:
            dwg.add(dwg.rect(insert=(x, y0), size=(panel_w, body_h), fill="none", stroke=brand["primary"], stroke_width=1.8))
            x += panel_w
        if _elements_enabled(elements, "fold_lines"):
            x = x0 + panels[0]
            for panel_w in panels[1:]:
                dwg.add(dwg.line(start=(x, y0), end=(x, y0 + body_h), stroke=brand["fold_line"], stroke_dasharray="8,6", stroke_width=1.2))
                x += panel_w
        logo_x, logo_y = x0 + body_w * 0.12, y0 + body_h * 0.15
        logo_w, logo_h = body_w * 0.76, body_h * 0.4

    elif ptype == "pouch":
        body_w = _mm(width_mm)
        body_h = _mm(height_mm)
        seal_h = _mm(max(depth_mm, 15))
        x0, y0 = margin, margin + seal_h
        dwg.add(dwg.rect(insert=(x0, y0), size=(body_w, body_h), fill="none", stroke=brand["primary"], stroke_width=2))
        dwg.add(dwg.rect(insert=(x0, y0 - seal_h), size=(body_w, seal_h), fill=brand["secondary"], fill_opacity=0.2, stroke=brand["secondary"]))
        dwg.add(dwg.text("SEAL", insert=(x0 + body_w / 2, y0 - seal_h / 2 + 4), fill=brand["text"], font_size=10, text_anchor="middle", font_family="Arial, sans-serif"))
        logo_x, logo_y = x0 + body_w * 0.12, y0 + body_h * 0.18
        logo_w, logo_h = body_w * 0.76, body_h * 0.42

    elif ptype == "tray":
        body_w, body_h = _mm(width_mm), _mm(height_mm)
        lip = _mm(max(depth_mm, 8))
        x0, y0 = margin, margin
        dwg.add(dwg.rect(insert=(x0, y0), size=(body_w, body_h), fill="none", stroke=brand["primary"], stroke_width=2.4))
        dwg.add(dwg.rect(insert=(x0 + lip, y0 + lip), size=(body_w - 2 * lip, body_h - 2 * lip), fill="none", stroke=brand["fold_line"], stroke_dasharray="5,4", stroke_width=1))
        logo_x, logo_y = x0 + lip + 8, y0 + lip + 8
        logo_w, logo_h = body_w - 2 * lip - 16, (body_h - 2 * lip) * 0.45

    else:  # round_label
        diameter = min(canvas_w, canvas_h) - (margin * 2)
        cx, cy = canvas_w / 2, canvas_h / 2
        dwg.add(dwg.circle(center=(cx, cy), r=diameter / 2, fill="none", stroke=brand["primary"], stroke_width=2.4))
        if _elements_enabled(elements, "cut_lines"):
            dwg.add(dwg.circle(center=(cx, cy), r=(diameter / 2) - 4, fill="none", stroke=brand["cut_line"], stroke_dasharray="4,4", stroke_width=1.0))
        logo_w, logo_h = diameter * 0.64, diameter * 0.22
        logo_x, logo_y = cx - (logo_w / 2), cy - (logo_h / 2) - 8
        body_w = body_h = diameter
        x0, y0 = cx - (diameter / 2), cy - (diameter / 2)

    if _elements_enabled(elements, "bleed"):
        _draw_bleed(dwg, x0, y0, body_w if ptype != "folding_box" else sum([_mm(depth_mm), _mm(width_mm), _mm(depth_mm), _mm(width_mm)]), body_h, brand, bleed_mm)

    if _elements_enabled(elements, "logo_area"):
        _draw_logo_block(dwg, logo_x, logo_y, logo_w, logo_h, brand, txt)

    if _elements_enabled(elements, "window"):
        wx = x0 + body_w * 0.55 if ptype != "round_label" else logo_x + logo_w * 0.1
        wy = y0 + body_h * 0.55 if ptype != "round_label" else logo_y + logo_h + 8
        _draw_window(dwg, wx, wy, max(60, body_w * 0.32), max(40, body_h * 0.22))

    if _elements_enabled(elements, "halal_badge"):
        _draw_halal_badge(dwg, x0 + 8, y0 + 8, brand)

    if _elements_enabled(elements, "nutrition_panel"):
        nx = x0 + 8
        ny = y0 + body_h - min(120, body_h * 0.45)
        _draw_nutrition_panel(dwg, nx, ny, min(150, body_w * 0.42), min(110, body_h * 0.4), brand, _nutrition_rows(spec))

    if _elements_enabled(elements, "ingredients") and txt["ingredients"]:
        _draw_ingredients(dwg, x0 + 8, y0 + body_h - 28, body_w - 16, txt["ingredients"], brand)

    if _elements_enabled(elements, "barcode"):
        barcode_uri = _barcode_data_uri(str(spec.get("barcode_value") or DEFAULT_BARCODE_VALUE))
        bar_w = max(140, body_w * 0.35)
        bar_h = max(50, body_h * 0.16)
        bar_x = x0 + body_w - bar_w - 14
        bar_y = y0 + body_h - bar_h - 14
        dwg.add(dwg.rect(insert=(bar_x - 4, bar_y - 4), size=(bar_w + 8, bar_h + 8), fill="#ffffff"))
        dwg.add(dwg.image(href=barcode_uri, insert=(bar_x, bar_y), size=(bar_w, bar_h)))

    if _elements_enabled(elements, "qr_code"):
        qr_uri = _qr_placeholder_svg(100)
        qr_s = min(90, body_w * 0.22)
        dwg.add(dwg.image(href=qr_uri, insert=(x0 + 10, y0 + body_h - qr_s - 10), size=(qr_s, qr_s)))

    if _elements_enabled(elements, "dimensions"):
        _draw_dimensions(dwg, x0, y0, body_w, body_h, spec, brand)

    design_name = spec.get("design_name") or txt["product_name"]
    dwg.add(
        dwg.text(
            f"Foodlinkk Packaging Studio · {design_name}"[:80],
            insert=(12, canvas_h - 10),
            fill=brand["muted"],
            font_size=9,
            font_family="Arial, sans-serif",
        )
    )

    return dwg.tostring()
