"""
compose_hero.py — compose the 1200×800px hero image for Proximus Loyalty Movie emails.

Composition:
  Layer 1: background (gradient | solid | blurred | ai_generated | image)
  Layer 2: poster — scaled to 800px height, pasted flush right, hard edge, unmodified
  Layer 3: title text — left zone, Proximus Bold, white, auto-size to fit
  Layer 4: subtitle text — left zone below title, Proximus Regular, #eeeaf4

Output: JPEG 80% quality, 1200×800px
"""
from __future__ import annotations

import pathlib
import warnings
from typing import Any

from PIL import Image, ImageDraw, ImageFilter, ImageFont

# ── Constants ────────────────────────────────────────────────────────────────

WIDTH, HEIGHT = 1200, 800
JPEG_QUALITY = 80

# Left text zone: x from 40 to 560 (right half is for the poster)
TEXT_ZONE_LEFT = 40
TEXT_ZONE_RIGHT = 540
TEXT_ZONE_WIDTH = TEXT_ZONE_RIGHT - TEXT_ZONE_LEFT

# Proximus brand colors
COLOR_PURPLE = (92, 45, 145)        # #5c2d91
COLOR_SOFT_PURPLE = (238, 234, 244)  # #eeeaf4
COLOR_BLACK = (0, 0, 0)
COLOR_WHITE = (255, 255, 255)

# Proximus branding assets — check both local dev layout (parents[2]) and VPS layout (parents[1])
_BRANDING_DIR = next(
    (p for p in [
        pathlib.Path(__file__).parents[2] / "Branding Guide",
        pathlib.Path(__file__).parents[1] / "Branding Guide",
    ] if p.exists()),
    pathlib.Path(__file__).parents[2] / "Branding Guide",
)
_LOGO_PATH = _BRANDING_DIR / "Proximus-logo-Vertical-BW-neg-RGB.png"
LOGO_HEIGHT = 120       # px — adjust to taste
LOGO_BOTTOM_MARGIN = 40 # px from bottom edge

# Proximus OTF fonts — already in the repo at this path
_FONTS_DIR = _BRANDING_DIR / "fonts"
_FONT_FILES = {
    "title":    _FONTS_DIR / "Proximus-Light.otf",
    "subtitle": _FONTS_DIR / "Proximus-Bold.otf",
}


def _ensure_fonts() -> dict[str, pathlib.Path | str]:
    """
    Return font paths from clients/Proximus/Branding Guide/fonts/.
    Falls back to Verdana with a warning if the directory is missing.
    """
    result = {}
    for key, path in _FONT_FILES.items():
        if path.exists():
            result[key] = path
        else:
            warnings.warn(
                f"Proximus font not found at {path} — falling back to Verdana.",
                stacklevel=2,
            )
            result[key] = "Verdana"
    return result


def _load_font(font_ref: pathlib.Path | str, size: int) -> ImageFont.FreeTypeFont:
    if isinstance(font_ref, pathlib.Path):
        return ImageFont.truetype(str(font_ref), size)
    try:
        return ImageFont.truetype(font_ref, size)
    except OSError:
        return ImageFont.load_default()


def _wrap_text(
    draw: ImageDraw.ImageDraw,
    text: str,
    font: ImageFont.FreeTypeFont,
    max_width: int,
) -> list[str]:
    """Word-wrap text into lines that each fit within max_width."""
    words = text.split()
    lines: list[str] = []
    current = ""
    for word in words:
        candidate = f"{current} {word}".strip()
        w = draw.textbbox((0, 0), candidate, font=font)[2]
        if w <= max_width:
            current = candidate
        else:
            if current:
                lines.append(current)
            current = word
    if current:
        lines.append(current)
    return lines


def _fit_wrapped_text(
    draw: ImageDraw.ImageDraw,
    text: str,
    font_path: pathlib.Path | str,
    max_width: int,
    start_size: int,
    max_lines: int = 3,
    min_size: int = 18,
) -> tuple[ImageFont.FreeTypeFont, list[str]]:
    """Find the largest font size where text wraps into at most max_lines lines."""
    size = start_size
    while size >= min_size:
        font = _load_font(font_path, size)
        lines = _wrap_text(draw, text, font, max_width)
        if len(lines) <= max_lines:
            return font, lines
        size -= 2
    font = _load_font(font_path, min_size)
    return font, _wrap_text(draw, text, font, max_width)


# Gradient PNG — check VPS layout (parents[1] = /opt/proximus) then local dev layout
_GRADIENT_PATH = next(
    (p for p in [
        pathlib.Path(__file__).parents[1] / "assets" / "gradient_1200x800.png",  # VPS: /opt/proximus/assets/
        pathlib.Path(__file__).parents[2] / "assets" / "gradient_1200x800.png",  # alt layout
        pathlib.Path(__file__).parents[3] / "output" / "gradient_1200x800.png",  # local dev: SecondBrain/output/
    ] if p.exists()),
    pathlib.Path(__file__).parents[1] / "assets" / "gradient_1200x800.png",
)


def build_gradient_background(width: int = WIDTH, height: int = HEIGHT) -> Image.Image:
    """Load the pre-made gradient PNG and resize to target dimensions."""
    if not _GRADIENT_PATH.exists():
        raise FileNotFoundError(
            f"Gradient background not found: {_GRADIENT_PATH}\n"
            "Place gradient_1200x800.png at that path."
        )
    img = Image.open(_GRADIENT_PATH).convert("RGB")
    if img.size != (width, height):
        img = img.resize((width, height), Image.LANCZOS)
    return img


def _build_solid_background(hex_color: str) -> Image.Image:
    hex_color = hex_color.lstrip("#")
    r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
    return Image.new("RGB", (WIDTH, HEIGHT), (r, g, b))


def _build_blurred_background(poster: Image.Image) -> Image.Image:
    bg = poster.copy().convert("RGB")
    bg = bg.resize((WIDTH, HEIGHT), Image.LANCZOS)
    bg = bg.filter(ImageFilter.GaussianBlur(radius=40))
    # Darken overlay for text legibility
    overlay = Image.new("RGB", (WIDTH, HEIGHT), (0, 0, 0))
    bg = Image.blend(bg, overlay, alpha=0.4)
    return bg


def _build_ai_background(path: str | None) -> Image.Image:
    if not path:
        raise ValueError("ai_background_path must be set when background_type is 'ai_generated'")
    img = Image.open(path).convert("RGB")
    # Crop-fit to 1200×800: center-crop
    img_ratio = img.width / img.height
    target_ratio = WIDTH / HEIGHT
    if img_ratio > target_ratio:
        new_w = int(img.height * target_ratio)
        offset = (img.width - new_w) // 2
        img = img.crop((offset, 0, offset + new_w, img.height))
    else:
        new_h = int(img.width / target_ratio)
        offset = (img.height - new_h) // 2
        img = img.crop((0, offset, img.width, offset + new_h))
    return img.resize((WIDTH, HEIGHT), Image.LANCZOS)


def _build_image_background(path: str | None) -> Image.Image:
    """
    Load a PNG from path and center-crop/resize to 1200×800.
    Used when background_type is 'image' (e.g., Proximus branded background asset).
    """
    if not path:
        raise ValueError(
            "background_image_path must be set when background_type is 'image'"
        )
    img = Image.open(path).convert("RGB")
    # Center-crop to target aspect ratio, then resize — identical logic to _build_ai_background
    img_ratio = img.width / img.height
    target_ratio = WIDTH / HEIGHT
    if img_ratio > target_ratio:
        new_w = int(img.height * target_ratio)
        offset = (img.width - new_w) // 2
        img = img.crop((offset, 0, offset + new_w, img.height))
    else:
        new_h = int(img.width / target_ratio)
        offset = (img.height - new_h) // 2
        img = img.crop((0, offset, img.width, offset + new_h))
    return img.resize((WIDTH, HEIGHT), Image.LANCZOS)


_POSTER_PADDING = 40  # px — used in "padded" fit mode


def _paste_poster(canvas: Image.Image, poster_path: str, fit: str = "full") -> None:
    """Scale poster and paste onto canvas.

    fit="full"   — scaled to full HEIGHT, flush right, no margin.
    fit="padded" — scaled to HEIGHT - 2*padding, 40px from right edge, centered vertically.
    """
    poster = Image.open(poster_path).convert("RGB")

    if fit == "padded":
        target_h = HEIGHT - 2 * _POSTER_PADDING
        scale = target_h / poster.height
        new_w = int(poster.width * scale)
        poster_resized = poster.resize((new_w, target_h), Image.LANCZOS)
        x_offset = WIDTH - new_w - _POSTER_PADDING
        y_offset = _POSTER_PADDING  # (HEIGHT - target_h) // 2 == _POSTER_PADDING
    else:
        scale = HEIGHT / poster.height
        new_w = int(poster.width * scale)
        poster_resized = poster.resize((new_w, HEIGHT), Image.LANCZOS)
        x_offset = WIDTH - new_w
        y_offset = 0

    canvas.paste(poster_resized, (x_offset, y_offset))


def _draw_text_layers(
    canvas: Image.Image,
    title: str,
    subtitle: str,
    fonts: dict[str, pathlib.Path | str],
) -> None:
    draw = ImageDraw.Draw(canvas)

    # Title — white, Proximus Bold, word-wrapped, up to 3 lines
    title_font, title_lines = _fit_wrapped_text(
        draw, title, fonts["title"], TEXT_ZONE_WIDTH, start_size=84, max_lines=3
    )
    line_h = draw.textbbox((0, 0), "Ag", font=title_font)[3]  # single line height
    line_gap = int(line_h * 0.2)

    title_y = 120  # start 120px from the top
    for line in title_lines:
        draw.text((TEXT_ZONE_LEFT, title_y), line, font=title_font, fill=COLOR_WHITE)
        title_y += line_h + line_gap

    sub_y = title_y + 32  # doubled gap between title block and subtitle

    # Subtitle — soft purple, Proximus Regular, word-wrapped, up to 2 lines
    sub_font, sub_lines = _fit_wrapped_text(
        draw, subtitle, fonts["subtitle"], TEXT_ZONE_WIDTH, start_size=36, max_lines=2
    )
    sub_line_h = draw.textbbox((0, 0), "Ag", font=sub_font)[3]
    for line in sub_lines:
        draw.text((TEXT_ZONE_LEFT, sub_y), line, font=sub_font, fill=COLOR_SOFT_PURPLE)
        sub_y += sub_line_h + int(sub_line_h * 0.15)


def _paste_logo(canvas: Image.Image) -> None:
    """Paste the Proximus logo bottom-left, aligned with the text zone."""
    if not _LOGO_PATH.exists():
        warnings.warn(f"Proximus logo not found at {_LOGO_PATH} — skipping.", stacklevel=2)
        return
    logo = Image.open(_LOGO_PATH).convert("RGBA")
    # Crop to visible (non-transparent) pixels so margin aligns with text
    bbox = logo.getbbox()
    if bbox:
        logo = logo.crop(bbox)
    scale = LOGO_HEIGHT / logo.height
    logo = logo.resize((int(logo.width * scale), LOGO_HEIGHT), Image.LANCZOS)
    x = TEXT_ZONE_LEFT
    y = HEIGHT - LOGO_HEIGHT - LOGO_BOTTOM_MARGIN
    canvas.paste(logo, (x, y), mask=logo)


def compose_hero(brief: dict[str, Any], *, lang: str, output_path: str) -> None:
    """
    Compose the hero image for the given language and save to output_path as JPEG 80%.

    brief keys used:
        poster_path, background_type, background_color, ai_background_path,
        background_image_path
        brief[lang]["hero_title"], brief[lang]["hero_subtitle"]
    """
    bg_type = brief.get("background_type", "gradient")
    poster_path = brief["poster_path"]
    loc = brief[lang]

    # 1. Background
    if bg_type == "gradient":
        canvas = build_gradient_background()
    elif bg_type == "solid":
        canvas = _build_solid_background(brief.get("background_color", "#000000"))
    elif bg_type == "blurred":
        poster_img = Image.open(poster_path).convert("RGB")
        canvas = _build_blurred_background(poster_img)
    elif bg_type == "ai_generated":
        canvas = _build_ai_background(brief.get("ai_background_path"))
    elif bg_type == "image":
        canvas = _build_image_background(brief.get("background_image_path"))
    else:
        raise ValueError(f"Unknown background_type: {bg_type!r}")

    # 2. Poster (hard edge, unmodified pixels)
    _paste_poster(canvas, poster_path, fit=brief.get("poster_fit", "full"))

    # 3. Text
    fonts = _ensure_fonts()
    _draw_text_layers(canvas, loc["hero_title"], loc["hero_subtitle"], fonts)

    # 4. Logo
    _paste_logo(canvas)

    # 5. Save
    pathlib.Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    canvas.save(output_path, "JPEG", quality=JPEG_QUALITY)
