"""Traduce un diseno de firma (sign_designs.params) a un sello visible de pyhanko.

Estrategia: componer el sello con Pillow replicando el diseno del usuario (imagen a
un lado + texto al otro, opacidades, fondo), y entregarlo como una imagen unica via
StaticStampStyle. Asi el resultado es fiel al diseno, en vez de depender del layout
de TextStampStyle (que solo centra la imagen de fondo detras del texto).

params relevantes:
  visible        -> si False, no hay sello (firma invisible)
  image_mode     -> 'left'|'right'|'top'|'bottom'|'background'|'none' : posicion de la imagen
  image_width    -> '40%' ancho de la imagen respecto al sello
  image_opacity  -> opacidad de la imagen [0..1]
  stamp_lines    -> lineas de texto (además de firmante/reason/location)
  firmante/reason/location -> texto
  add_date       -> añade la fecha
  box            -> tamaño del sello en puntos PDF (para la resolucion de composicion)
  fill_color / fill_opacity -> fondo del sello
  border / border_width     -> borde
"""
from __future__ import annotations
import os

# resolucion de composicion: pixeles por punto PDF (para nitidez)
_PX_PER_PT = 3.0
_FONT_REG = "/usr/share/fonts/dejavu/DejaVuSans.ttf"
_FONT_BOLD = "/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf"


def _val(params, key):
    v = params.get(key)
    if isinstance(v, dict):
        return v.get("value") or ""
    return v or ""


def _hex_rgb(s, default=(255, 255, 255)):
    try:
        s = (s or "").lstrip("#")
        if len(s) == 6:
            return tuple(int(s[i:i+2], 16) for i in (0, 2, 4))
    except Exception:
        pass
    return default


import re as _re

_VAR_RE = _re.compile(r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}")


def extract_variables(params: dict) -> list:
    """Devuelve la lista de nombres de variables ${x} presentes en las stamp_lines
    del diseno, en orden de aparicion y sin duplicados."""
    seen = []
    for ln in (params.get("stamp_lines") or []):
        for m in _VAR_RE.finditer(str(ln or "")):
            name = m.group(1)
            if name not in seen:
                seen.append(name)
    return seen


def _interpolate(text: str, values: dict) -> str:
    """Sustituye ${var} por su valor. Si falta el valor, deja el placeholder vacio."""
    def repl(m):
        return str(values.get(m.group(1), "")) if values else ""
    return _VAR_RE.sub(repl, str(text or ""))


def build_stamp_style(params: dict, *, image_full_path: str | None = None, variables: dict | None = None):
    """Devuelve un StaticStampStyle con el sello ya compuesto, o None si no es visible."""
    if not params or not params.get("visible", False):
        return None

    from PIL import Image, ImageDraw, ImageFont
    from pyhanko.stamp import StaticStampStyle

    # --- dimensiones del sello (desde box, en puntos PDF) ---
    box = params.get("box") or {}
    try:
        w_pt = float(box["x2"]) - float(box["x1"])
        h_pt = float(box["y2"]) - float(box["y1"])
    except Exception:
        w_pt, h_pt = 400.0, 120.0
    if w_pt <= 0 or h_pt <= 0:
        w_pt, h_pt = 400.0, 120.0

    W = max(60, int(w_pt * _PX_PER_PT))
    H = max(30, int(h_pt * _PX_PER_PT))

    # --- lienzo transparente (RGBA) ---
    fill_op = params.get("fill_opacity", params.get("bg_opacity", 0))
    try:
        fill_op = float(fill_op)
    except Exception:
        fill_op = 0.0
    fill_rgb = _hex_rgb(params.get("fill_color"), (255, 255, 255))
    bg = fill_rgb + (int(255 * fill_op),)
    img = Image.new("RGBA", (W, H), bg)
    draw = ImageDraw.Draw(img)

    # --- borde ---
    if params.get("border"):
        try:
            bw = max(1, int(float(params.get("border_width", 1)) * _PX_PER_PT / 2))
        except Exception:
            bw = 1
        draw.rectangle([0, 0, W-1, H-1], outline=(120, 120, 120, 255), width=bw)

    # --- layout: replica EXACTA del previsualizador (stampHTML en app.js) ---
    # El preview usa flex align-items:center (centrado vertical de imagen y texto),
    # gap:5px, padding:4px, y la imagen con background-size:contain (cabe en su celda
    # respetando proporcion, alineada left-center). Reproducimos eso aqui.
    image_mode = (params.get("image_mode") or "left").lower()
    # padding y gap del preview (en px de su viewport ~ proporcional). Escalamos al
    # tamaño del sello con un factor suave.
    PAD = max(2, int(4 * _PX_PER_PT / 2))
    GAP = max(2, int(5 * _PX_PER_PT / 2))
    inner_x0, inner_y0 = PAD, PAD
    inner_w, inner_h = W - 2*PAD, H - 2*PAD

    text_x0 = inner_x0
    text_w = inner_w

    if image_full_path and os.path.isfile(image_full_path) and image_mode not in ("none",):
        try:
            logo = Image.open(image_full_path).convert("RGBA")
            op = params.get("image_opacity", 1)
            try: op = float(op)
            except Exception: op = 1.0
            if op < 1.0:
                alpha = logo.split()[3].point(lambda a: int(a * op))
                logo.putalpha(alpha)

            iw = params.get("image_width", "40%")
            try: frac = float(str(iw).replace("%", "")) / 100.0
            except Exception: frac = 0.4
            frac = min(max(frac, 0.05), 0.95)

            if image_mode == "background":
                # background: contain, centrado, ocupa todo el sello
                cw, ch = W, H
                scale = min(cw / logo.width, ch / logo.height)
                tw_, th_ = max(1,int(logo.width*scale)), max(1,int(logo.height*scale))
                logo2 = logo.resize((tw_, th_), Image.LANCZOS)
                img.alpha_composite(logo2, ((W-tw_)//2, (H-th_)//2))
                # el texto va centrado sobre todo el sello
                text_x0, text_w = inner_x0, inner_w
            else:
                # celda de la imagen: ancho = frac * inner_w, alto = inner_h (100%)
                cell_w = int(inner_w * frac)
                cell_h = inner_h
                # CONTAIN: la imagen cabe dentro de la celda respetando proporcion
                scale = min(cell_w / logo.width, cell_h / logo.height)
                tw_ = max(1, int(logo.width * scale))
                th_ = max(1, int(logo.height * scale))
                logo = logo.resize((tw_, th_), Image.LANCZOS)

                if image_mode == "right":
                    cell_x = inner_x0 + inner_w - cell_w
                    # imagen alineada a la derecha, centrada vertical (right center)
                    ix = cell_x + (cell_w - tw_)
                    iy = inner_y0 + (cell_h - th_)//2
                    img.alpha_composite(logo, (ix, iy))
                    text_x0 = inner_x0
                    text_w = inner_w - cell_w - GAP
                elif image_mode == "top":
                    # imagen arriba centrada; texto debajo (caso menos usado)
                    ix = inner_x0 + (inner_w - tw_)//2
                    img.alpha_composite(logo, (ix, inner_y0))
                    text_x0 = inner_x0; text_w = inner_w
                else:  # left (por defecto): imagen izquierda, left center
                    cell_x = inner_x0
                    ix = cell_x  # left
                    iy = inner_y0 + (cell_h - th_)//2  # center vertical
                    img.alpha_composite(logo, (ix, iy))
                    text_x0 = inner_x0 + cell_w + GAP
                    text_w = inner_w - cell_w - GAP
        except Exception:
            pass

    text_w = max(20, text_w)

    # --- texto ---
    lines = []
    for ln in (params.get("stamp_lines") or []):
        if ln is not None and str(ln) != "":
            lines.append(_interpolate(str(ln), variables))
    if params.get("add_date"):
        from datetime import datetime, timezone
        ts = datetime.now(timezone.utc).strftime("%Y/%m/%d %H:%M:%S UTC")
        lines.append(ts)

    if lines:
        n = len(lines)
        # tamaño de fuente: que el bloque quepa en inner_h con interlineado 1.3
        LEAD = 1.3
        fs = int(inner_h / (n * LEAD))
        fs = max(9, min(fs, int(15 * _PX_PER_PT / 2)))
        try:
            font = ImageFont.truetype(_FONT_REG, fs)
        except Exception:
            font = ImageFont.load_default()

        line_h = int(fs * LEAD)
        block_h = line_h * n
        # CENTRADO VERTICAL del bloque de texto (align-items:center del preview)
        ty = inner_y0 + max(0, (inner_h - block_h)//2)

        top = params.get("text_opacity", 1)
        try: top = float(top)
        except Exception: top = 1.0
        text_col = (0, 0, 0, int(255*top))

        for ln in lines:
            draw.text((text_x0, ty), ln, fill=text_col, font=font)
            ty += line_h

    # --- guardar a PNG temporal y devolver StaticStampStyle ---
    import tempfile
    tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
    img.save(tmp.name, "PNG")
    tmp.close()

    from pyhanko.pdf_utils import images
    from pyhanko.pdf_utils.layout import SimpleBoxLayoutRule, AxisAlignment, InnerScaling, Margins
    # el PNG ya tiene las proporciones del box: lo estiramos para llenar la caja del sello
    layout = SimpleBoxLayoutRule(
        x_align=AxisAlignment.ALIGN_MID, y_align=AxisAlignment.ALIGN_MID,
        margins=Margins(left=0, right=0, top=0, bottom=0),
        inner_content_scaling=InnerScaling.STRETCH_TO_FIT)
    return StaticStampStyle(
        background=images.PdfImage(tmp.name),
        background_layout=layout,
        background_opacity=1.0,
        border_width=0)


def describe_variables(params: dict) -> dict:
    """Describe las variables dinamicas de un diseno para la API GET /design/{key}.
    Devuelve {text:[...], image: {name}|None}. Las de texto son las ${x} de las
    stamp_lines; la de imagen existe si image_source=='var'."""
    text_vars = extract_variables(params)
    image_var = None
    if (params.get("image_source") or "file") == "var" and (params.get("image_mode") or "") != "none":
        image_var = params.get("image_var_name") or "image"
    return {"text": text_vars, "image": image_var}
