import numpy as np
from PIL import Image, ImageDraw, ImageFont

from app.config import ASSETS_DIR, CATEGORIES

BG_WIDTH, BG_HEIGHT = 2160, 3840
OUT_WIDTH, OUT_HEIGHT = 1080, 1920

GRADIENT_TOP_LEFT = (91, 101, 115)
GRADIENT_BOTTOM_RIGHT = (15, 17, 31)

BADGE_CENTER_Y = 300
BADGE_PAD_X = 70
BADGE_PAD_Y = 40
BADGE_BORDER_WIDTH = 6

LOGO_CENTER = (int(BG_WIDTH * 0.85), int(BG_HEIGHT * 0.93))
LOGO_DIAMETER = int(BG_WIDTH * 0.19)
LOGO_OPACITY = 0.80

TEXT_MAX_WIDTH_RATIO = 0.88
TEXT_MAX_HEIGHT_RATIO = 0.35
TEXT_CENTER_Y_RATIO = 0.50
TEXT_START_FONT_SIZE = 74
TEXT_MIN_FONT_SIZE = 34
TEXT_LINE_SPACING = 1.25

FONT_REGULAR = ASSETS_DIR / "fonts" / "Poppins-Regular.ttf"
FONT_MEDIUM = ASSETS_DIR / "fonts" / "Poppins-Medium.ttf"
FONT_SEMIBOLD = ASSETS_DIR / "fonts" / "Poppins-SemiBold.ttf"


def _hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
    hex_color = hex_color.lstrip("#")
    return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))


def _diagonal_gradient(width: int, height: int, top_left, bottom_right) -> Image.Image:
    x = np.linspace(0, 1, width)
    y = np.linspace(0, 1, height)
    xv, yv = np.meshgrid(x, y)
    t = (xv + yv) / 2.0

    top_left = np.array(top_left, dtype=np.float32)
    bottom_right = np.array(bottom_right, dtype=np.float32)
    gradient = top_left[None, None, :] + t[:, :, None] * (bottom_right - top_left)[None, None, :]
    gradient = gradient.astype(np.uint8)

    return Image.fromarray(gradient, mode="RGB").convert("RGBA")


def _draw_badge(draw: ImageDraw.ImageDraw, category_slug: str) -> None:
    category = CATEGORIES[category_slug]
    color = _hex_to_rgb(category["color"])
    label = category["display_name"].upper()

    font = ImageFont.truetype(str(FONT_MEDIUM), 52)
    bbox = draw.textbbox((0, 0), label, font=font)
    text_w = bbox[2] - bbox[0]
    text_h = bbox[3] - bbox[1]

    badge_w = text_w + 2 * BADGE_PAD_X
    badge_h = text_h + 2 * BADGE_PAD_Y
    cx = BG_WIDTH // 2
    cy = BADGE_CENTER_Y

    left, top = cx - badge_w // 2, cy - badge_h // 2
    right, bottom = cx + badge_w // 2, cy + badge_h // 2

    draw.rounded_rectangle(
        [left, top, right, bottom],
        radius=badge_h // 2,
        outline=color,
        width=BADGE_BORDER_WIDTH,
    )
    draw.text(
        (cx - text_w // 2 - bbox[0], cy - text_h // 2 - bbox[1]),
        label,
        font=font,
        fill=color,
    )


def _paste_logo(bg: Image.Image) -> None:
    logo_path = ASSETS_DIR / "logo" / "channel_logo.png"
    logo = Image.open(logo_path).convert("RGBA")
    logo = logo.resize((LOGO_DIAMETER, LOGO_DIAMETER), Image.LANCZOS)

    alpha = logo.getchannel("A").point(lambda a: int(a * LOGO_OPACITY))
    logo.putalpha(alpha)

    x = LOGO_CENTER[0] - LOGO_DIAMETER // 2
    y = LOGO_CENTER[1] - LOGO_DIAMETER // 2
    bg.alpha_composite(logo, (x, y))


def build_background_layer(category_slug: str) -> Image.Image:
    bg = _diagonal_gradient(BG_WIDTH, BG_HEIGHT, GRADIENT_TOP_LEFT, GRADIENT_BOTTOM_RIGHT)
    draw = ImageDraw.Draw(bg)
    _draw_badge(draw, category_slug)
    _paste_logo(bg)
    return bg


def _wrap_text(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.FreeTypeFont, max_width: int) -> list[str]:
    words = text.split()
    lines: list[str] = []
    current = ""
    for word in words:
        candidate = f"{current} {word}".strip()
        bbox = draw.textbbox((0, 0), candidate, font=font)
        if bbox[2] - bbox[0] <= max_width or not current:
            current = candidate
        else:
            lines.append(current)
            current = word
    if current:
        lines.append(current)
    return lines


def build_text_layer(fact_text: str) -> Image.Image:
    img = Image.new("RGBA", (OUT_WIDTH, OUT_HEIGHT), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)

    max_width = int(OUT_WIDTH * TEXT_MAX_WIDTH_RATIO)
    max_height = int(OUT_HEIGHT * TEXT_MAX_HEIGHT_RATIO)

    font_size = TEXT_START_FONT_SIZE
    lines: list[str] = []
    font: ImageFont.FreeTypeFont | None = None
    line_height = 0

    while font_size >= TEXT_MIN_FONT_SIZE:
        font = ImageFont.truetype(str(FONT_SEMIBOLD), font_size)
        lines = _wrap_text(draw, fact_text, font, max_width)
        bbox = draw.textbbox((0, 0), "Ağ", font=font)
        line_height = int((bbox[3] - bbox[1]) * TEXT_LINE_SPACING)
        total_height = line_height * len(lines)
        if total_height <= max_height:
            break
        font_size -= 4

    total_height = line_height * len(lines)
    start_y = int(OUT_HEIGHT * TEXT_CENTER_Y_RATIO) - total_height // 2

    for i, line in enumerate(lines):
        bbox = draw.textbbox((0, 0), line, font=font)
        line_w = bbox[2] - bbox[0]
        x = (OUT_WIDTH - line_w) // 2 - bbox[0]
        y = start_y + i * line_height
        draw.text((x, y), line, font=font, fill=(255, 255, 255, 255))

    return img
