"""
Pillow-based calendar card renderer.

Renders a 1400×auto image with a 7-column daily broadcast grid.
Today's column is highlighted in orange.
"""
from __future__ import annotations

import asyncio

import aiohttp
from PIL import Image, ImageDraw

from .font_manager import get_font
from .image_utils import (
    fetch_image,
    fit_cover,
    image_to_base64,
    placeholder_cover,
    rounded_clip,
    strip_supplementary,
    text_line_height,
    text_width,
    wrap_text,
)

# ── Palette ────────────────────────────────────────────────────────────────────
BG_PAGE = (240, 242, 245)
WHITE = (255, 255, 255)
PRIMARY = (251, 140, 0)
TEXT_MAIN = (26, 26, 26)
TEXT_SUB = (133, 144, 166)
TEXT_LIGHT = (153, 153, 153)
BORDER = (234, 234, 234)
COL_BG = (255, 255, 255, 200)  # slightly translucent column bg (drawn as solid)

# ── Layout constants ───────────────────────────────────────────────────────────
CANVAS_W = 1400
CANVAS_PAD = 30
GRID_GAP = 16
COLS = 7
COL_W = (CANVAS_W - 2 * CANVAS_PAD - (COLS - 1) * GRID_GAP) // COLS  # ≈ 177

DAY_HEADER_H = 66       # day column header
ITEM_PAD = 10           # horizontal padding inside an anime item
COVER_ASPECT = 1.5      # height = width * COVER_ASPECT  (2∶3)
COVER_W = COL_W - 2 * ITEM_PAD
COVER_H = int(COVER_W * COVER_ASPECT)
INFO_H = 78             # fixed height for title + score/rank under cover
ITEM_H = ITEM_PAD + COVER_H + ITEM_PAD + INFO_H + ITEM_PAD
SEPARATOR = 1

HEADER_AREA_H = 64      # "每日放送表" title bar
HEADER_GAP = 20         # gap between header and grid

MAX_CONCURRENT_FETCH = 10


# ── Helpers ────────────────────────────────────────────────────────────────────

async def _fetch_all(
    items_by_col: list[list[dict]],
    session: aiohttp.ClientSession | None,
) -> list[list[Image.Image | None]]:
    """Fetch all cover images concurrently (capped at MAX_CONCURRENT_FETCH)."""
    sem = asyncio.Semaphore(MAX_CONCURRENT_FETCH)

    async def _fetch_one(url: str) -> Image.Image | None:
        async with sem:
            return await fetch_image(url, session, timeout=10)

    tasks: list[asyncio.Task[Image.Image | None]] = []
    for items in items_by_col:
        for item in items:
            images = item.get("images") or {}
            url = str(
                images.get("common") or images.get("large") or images.get("medium") or ""
            )
            tasks.append(asyncio.create_task(_fetch_one(url)))

    results_flat = await asyncio.gather(*tasks, return_exceptions=True)

    # Re-group by column
    images_by_col: list[list[Image.Image | None]] = []
    idx = 0
    for items in items_by_col:
        col_imgs: list[Image.Image | None] = []
        for _ in items:
            r = results_flat[idx]
            col_imgs.append(r if isinstance(r, Image.Image) else None)
            idx += 1
        images_by_col.append(col_imgs)
    return images_by_col


# ── Main renderer ──────────────────────────────────────────────────────────────

async def draw_calendar_card(
    data: dict,
    session: aiohttp.ClientSession | None = None,
) -> str | None:
    """Render the weekly broadcast calendar and return a base64-encoded PNG string."""
    try:
        return await _render(data, session)
    except Exception as exc:
        try:
            from astrbot.api import logger
            logger.error(f"[-] Pillow calendar card render failed: {exc}")
        except Exception:
            pass
        return None


async def _render(data: dict, session: aiohttp.ClientSession | None) -> str | None:  # noqa: C901
    days: list[dict] = data.get("days") or []
    if not days:
        return None

    # Normalise to exactly 7 columns (pad with empty if needed)
    while len(days) < COLS:
        days.append({"weekday": {"cn": "", "en": ""}, "items": [], "is_today": False})

    items_by_col: list[list[dict]] = [
        [item for item in (day.get("items") or []) if isinstance(item, dict)]
        for day in days
    ]

    # ── Fetch all covers concurrently ──────────────────────────────────────────
    images_by_col = await _fetch_all(items_by_col, session)

    # ── Compute column heights ─────────────────────────────────────────────────
    col_heights = [
        DAY_HEADER_H + len(items) * (ITEM_H + SEPARATOR) + GRID_GAP
        for items in items_by_col
    ]
    max_col_h = max(col_heights) if col_heights else DAY_HEADER_H + 200

    # ── Canvas ─────────────────────────────────────────────────────────────────
    canvas_h = CANVAS_PAD + HEADER_AREA_H + HEADER_GAP + max_col_h + CANVAS_PAD
    canvas = Image.new("RGBA", (CANVAS_W, canvas_h), (*BG_PAGE, 255))
    d = ImageDraw.Draw(canvas)

    # ── Fonts ──────────────────────────────────────────────────────────────────
    F = {
        "h_title": get_font(28, bold=True),
        "h_sub": get_font(14),
        "day_cn": get_font(16, bold=True),
        "day_en": get_font(11),
        "item_title": get_font(13, bold=True),
        "score": get_font(13, bold=True),
        "rank": get_font(11),
    }

    # ── Page header: "每日放送表" ─────────────────────────────────────────────
    hx = CANVAS_PAD + 10
    hy = CANVAS_PAD
    # Orange accent bar
    d.rounded_rectangle([hx, hy + 4, hx + 7, hy + 36], radius=3, fill=PRIMARY)
    d.text((hx + 18, hy), "每日放送表", font=F["h_title"], fill=TEXT_MAIN)
    sub_x = hx + 18 + text_width("每日放送表", F["h_title"]) + 14
    d.text((sub_x, hy + 8), "Bangumi Calendar", font=F["h_sub"], fill=TEXT_SUB)

    # ── Day columns ────────────────────────────────────────────────────────────
    grid_top = CANVAS_PAD + HEADER_AREA_H + HEADER_GAP

    for col_idx, day in enumerate(days):
        cx = CANVAS_PAD + col_idx * (COL_W + GRID_GAP)
        cy = grid_top
        is_today: bool = bool(day.get("is_today"))
        items = items_by_col[col_idx]
        col_imgs = images_by_col[col_idx]
        col_h = max_col_h  # all columns same height for visual alignment

        weekday = day.get("weekday") or {}
        day_cn = strip_supplementary(str(weekday.get("cn") or ""))
        day_en = str(weekday.get("en") or "").upper()

        # Column background
        col_fill: tuple[int, int, int] = WHITE
        col_outline = (180, 180, 180) if not is_today else PRIMARY
        border_w = 1 if not is_today else 2
        d.rounded_rectangle(
            [cx, cy, cx + COL_W, cy + col_h],
            radius=14,
            fill=col_fill,
            outline=col_outline,
            width=border_w,
        )

        # Day header
        header_fill = PRIMARY if is_today else WHITE
        header_text = WHITE if is_today else TEXT_MAIN
        d.rounded_rectangle(
            [cx, cy, cx + COL_W, cy + DAY_HEADER_H],
            radius=14,
            fill=header_fill,
        )
        # Flatten bottom corners of header by overdrawing a rect
        d.rectangle(
            [cx, cy + DAY_HEADER_H - 14, cx + COL_W, cy + DAY_HEADER_H],
            fill=header_fill,
        )
        cn_w = text_width(day_cn, F["day_cn"])
        d.text((cx + (COL_W - cn_w) // 2, cy + 14), day_cn,
               font=F["day_cn"], fill=header_text)
        en_w = text_width(day_en, F["day_en"])
        d.text((cx + (COL_W - en_w) // 2, cy + 14 + text_line_height(F["day_cn"]) + 4),
               day_en, font=F["day_en"], fill=(*header_text[:3], 180))

        # Anime items
        item_y = cy + DAY_HEADER_H + SEPARATOR

        if not items:
            d.text(
                (cx + 16, item_y + 40),
                "今日无更新",
                font=F["day_en"],
                fill=TEXT_LIGHT,
            )
        else:
            for item_idx, item in enumerate(items):
                img = col_imgs[item_idx] if item_idx < len(col_imgs) else None

                # Separator line between items (skip for first)
                if item_idx > 0:
                    d.line([cx + 1, item_y, cx + COL_W - 1, item_y], fill=BORDER)

                # Cover image
                cover_top = item_y + ITEM_PAD
                if img:
                    cover = fit_cover(img, COVER_W, COVER_H)
                else:
                    cover = placeholder_cover(COVER_W, COVER_H, color=(230, 230, 230))
                clipped = rounded_clip(cover, 8)
                canvas.alpha_composite(clipped, (cx + ITEM_PAD, cover_top))

                # Info section
                info_top = cover_top + COVER_H + ITEM_PAD
                name_cn = strip_supplementary(str(item.get("name_cn") or item.get("name") or ""))
                name_lines = wrap_text(name_cn, F["item_title"], COL_W - 2 * ITEM_PAD)[:2]
                LH = text_line_height(F["item_title"]) + 3
                for line_idx, line in enumerate(name_lines):
                    d.text((cx + ITEM_PAD, info_top + line_idx * LH), line,
                           font=F["item_title"], fill=TEXT_MAIN)

                # Score + rank
                meta_y = info_top + 2 * LH + 6
                rating = item.get("rating") if isinstance(item.get("rating"), dict) else {}
                score = rating.get("score") if rating else None
                rank = item.get("rank")
                meta_x = cx + ITEM_PAD
                if score:
                    score_txt = f"★ {score}"
                    d.text((meta_x, meta_y), score_txt, font=F["score"], fill=PRIMARY)
                    meta_x += text_width(score_txt, F["score"]) + 10
                if rank:
                    rank_txt = f"#{rank}"
                    rank_tw = text_width(rank_txt, F["rank"]) + 12
                    d.rounded_rectangle(
                        [meta_x, meta_y, meta_x + rank_tw, meta_y + text_line_height(F["rank"]) + 4],
                        radius=4,
                        fill=(245, 245, 245),
                    )
                    d.text((meta_x + 6, meta_y + 2), rank_txt, font=F["rank"], fill=TEXT_LIGHT)

                item_y += ITEM_H + SEPARATOR

    return image_to_base64(canvas)
