"""
Pillow-based subject card renderer.

Renders a 800×auto card with:
  - Left column (210px): cover image · episode progress grid · rating histogram
  - Right column (514px): title · score/rank · tags · summary · footer
"""
from __future__ import annotations

import asyncio

import aiohttp
from PIL import Image, ImageDraw, ImageFilter

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 ────────────────────────────────────────────────────────────────────
WHITE = (255, 255, 255)
PRIMARY = (251, 140, 0)
SECONDARY_BG = (255, 243, 224)
TEXT_MAIN = (26, 26, 26)
TEXT_SUB = (133, 144, 166)
TEXT_LIGHT = (153, 153, 153)
BORDER = (234, 234, 234)
EP_GRAY_BG = (232, 232, 232)
EP_GRAY_TEXT = (102, 102, 102)
EP_ORANGE = (255, 152, 0)
ORANGE_DEEP = (230, 81, 0)
FOOTER_BG = (249, 249, 249)
HIST_LOW = (255, 224, 178)   # low scores (1-6)

# ── Layout constants ───────────────────────────────────────────────────────────
CARD_W = 800
CARD_PAD = 24
LEFT_W = 210
COL_GAP = 28
RIGHT_W = CARD_W - CARD_PAD * 2 - LEFT_W - COL_GAP  # = 514
COVER_W = LEFT_W
COVER_H = 315   # 2∶3 default
SHADOW_PAD = 20
SHADOW_BLUR = 12


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

def _draw_rounded_rect(
    draw: ImageDraw.ImageDraw,
    box: tuple[int, int, int, int],
    radius: int,
    fill: tuple | None = None,
    outline: tuple | None = None,
    width: int = 1,
) -> None:
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)


def _make_shadow(card_w: int, card_h: int) -> Image.Image:
    """Return an RGBA image containing only the blurred shadow."""
    sw = card_w + SHADOW_PAD * 2
    sh = card_h + SHADOW_PAD * 2
    shadow = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
    ImageDraw.Draw(shadow).rounded_rectangle(
        [SHADOW_PAD, SHADOW_PAD + 8, SHADOW_PAD + card_w, SHADOW_PAD + card_h + 8],
        radius=20,
        fill=(0, 0, 0, 28),
    )
    return shadow.filter(ImageFilter.GaussianBlur(SHADOW_BLUR))


def _int_rating_count(rating_count: object, key: int) -> int:
    if not isinstance(rating_count, dict):
        return 0
    v = rating_count.get(str(key)) or rating_count.get(key) or 0
    return int(v) if isinstance(v, (int, float)) else 0


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

async def draw_subject_card(
    data: dict,
    session: aiohttp.ClientSession | None = None,
) -> str | None:
    """Render a subject info card 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 subject card render failed: {exc}")
        except Exception:
            pass
        return None


async def _render(data: dict, session: aiohttp.ClientSession | None) -> str | None:  # noqa: C901
    # ── Extract fields ─────────────────────────────────────────────────────────
    name_cn: str = strip_supplementary(str(data.get("name_cn") or ""))
    name: str = strip_supplementary(str(data.get("name") or ""))
    title = name_cn or name
    subtitle = name if (name_cn and name and name != name_cn) else ""

    image_url = str(data.get("image_url") or "")
    rating = data.get("rating") if isinstance(data.get("rating"), dict) else {}
    score = rating.get("score") if rating else None
    rating_total = rating.get("total") if rating else None
    rating_rank = rating.get("rank") if rating else None
    rating_count = rating.get("count") if rating else {}
    rank = data.get("rank") or rating_rank

    tags_raw = data.get("tags") or []
    tags = [strip_supplementary(str(t.get("name", ""))) for t in tags_raw[:8] if isinstance(t, dict)]
    tags = [t for t in tags if t]

    summary = strip_supplementary(str(data.get("summary") or "暂无简介"))
    date_str = strip_supplementary(str(data.get("date") or ""))
    platform = strip_supplementary(str(data.get("platform") or ""))
    # strip leading emoji like "🎬 " from platform
    platform = platform.lstrip()

    subject_id = data.get("id")
    episode_list: list[dict] = [ep for ep in (data.get("episode_list") or []) if isinstance(ep, dict)]
    air_weekday = strip_supplementary(str(data.get("air_weekday") or ""))
    collection = data.get("collection") if isinstance(data.get("collection"), dict) else {}
    collection_doing = collection.get("doing") if collection else None

    # ── Fonts ──────────────────────────────────────────────────────────────────
    F = {
        "title": get_font(26, bold=True),
        "subtitle": get_font(13),
        "score": get_font(34, bold=True),
        "star": get_font(22),
        "rank_tag": get_font(13, bold=True),
        "count": get_font(13),
        "tag": get_font(12),
        "sum_label": get_font(13, bold=True),
        "summary": get_font(14),
        "footer": get_font(12),
        "ep_label": get_font(10, bold=True),
        "ep_cell": get_font(10, bold=True),
        "chart_title": get_font(10, bold=True),
        "weekday": get_font(20, bold=True),
        "weekday_sub": get_font(9),
    }

    # ── Fetch cover image ──────────────────────────────────────────────────────
    cover_img = await fetch_image(image_url, session, timeout=10)

    # ── Compute left column height ─────────────────────────────────────────────
    if cover_img:
        ratio = cover_img.width / cover_img.height
        cover_h = max(int(COVER_W / ratio), COVER_H)
        cover_h = min(cover_h, 420)
    else:
        cover_h = COVER_H

    ep_block_h = 0
    CELLS_PER_ROW = 6
    CELL = 28
    CELL_GAP = 4
    if episode_list:
        n_rows = (len(episode_list) + CELLS_PER_ROW - 1) // CELLS_PER_ROW
        ep_block_h = 10 + text_line_height(F["ep_label"]) + 8 + n_rows * (CELL + CELL_GAP) - CELL_GAP + 10

    hist_block_h = 0
    has_hist = isinstance(rating_count, dict) and any(
        _int_rating_count(rating_count, i) for i in range(1, 11)
    )
    if has_hist:
        hist_block_h = 12 + text_line_height(F["chart_title"]) + 4 + 45 + 2 + text_line_height(F["chart_title"]) + 12

    left_h = cover_h
    if ep_block_h:
        left_h += 12 + ep_block_h
    if hist_block_h:
        left_h += 12 + hist_block_h

    # ── Compute right column height ────────────────────────────────────────────
    LH_title = text_line_height(F["title"]) + 4
    title_lines = wrap_text(title, F["title"], RIGHT_W - 70)[:3]
    title_block_h = len(title_lines) * LH_title

    subtitle_block_h = (text_line_height(F["subtitle"]) + 6) if subtitle else 0

    score_row_h = 46

    # Tags height (flex-wrap)
    tag_row_h = text_line_height(F["tag"]) + 8
    tag_block_h = 0
    if tags:
        row_x = 0
        rows = 1
        for tag in tags:
            tw = text_width(tag, F["tag"]) + 24
            if row_x + tw + 8 > RIGHT_W:
                rows += 1
                row_x = tw + 8
            else:
                row_x += tw + 8
        tag_block_h = rows * tag_row_h + (rows - 1) * 8

    # Summary
    LH_sum = int(text_line_height(F["summary"]) * 1.75)
    summary_lines = wrap_text(summary, F["summary"], RIGHT_W)[:7]
    summary_block_h = (
        20                                            # border-top gap
        + text_line_height(F["sum_label"]) + 8        # label
        + len(summary_lines) * LH_sum
    )

    footer_block_h = 16 + 32

    right_h = (
        16
        + title_block_h
        + (subtitle_block_h if subtitle_block_h else 0)
        + 16
        + score_row_h
        + 16
        + (tag_block_h + 16 if tag_block_h else 0)
        + summary_block_h
        + footer_block_h
    )

    # ── Create card surface ────────────────────────────────────────────────────
    inner_h = max(left_h, right_h, 360)
    card_h = inner_h + CARD_PAD * 2

    card = Image.new("RGBA", (CARD_W, card_h), (255, 255, 255, 255))
    d = ImageDraw.Draw(card)
    d.rounded_rectangle([0, 0, CARD_W - 1, card_h - 1], radius=20, fill=WHITE)

    # ── LEFT COLUMN ───────────────────────────────────────────────────────────
    lx = CARD_PAD
    ly = CARD_PAD

    # Cover
    if cover_img:
        raw = fit_cover(cover_img, COVER_W, cover_h)
    else:
        raw = placeholder_cover(COVER_W, cover_h)
    card.alpha_composite(rounded_clip(raw, 12), (lx, ly))

    cur_ly = ly + cover_h + 12

    # Episode grid block
    if ep_block_h and episode_list:
        _draw_rounded_rect(d, (lx, cur_ly, lx + LEFT_W, cur_ly + ep_block_h), radius=10,
                           fill=WHITE, outline=BORDER, width=1)
        # Label row
        label_y = cur_ly + 10
        d.text((lx + 10, label_y), "放送进度", font=F["ep_label"], fill=TEXT_LIGHT)
        aired_count = sum(1 for ep in episode_list if ep.get("aired"))
        prog = f"{aired_count} / {len(episode_list)}"
        pw = text_width(prog, F["ep_label"])
        d.text((lx + LEFT_W - 10 - pw, label_y), prog, font=F["ep_label"], fill=PRIMARY)

        grid_y = label_y + text_line_height(F["ep_label"]) + 8
        for i, ep_item in enumerate(episode_list):
            col = i % CELLS_PER_ROW
            row = i // CELLS_PER_ROW
            cx = lx + 10 + col * (CELL + CELL_GAP)
            cy = grid_y + row * (CELL + CELL_GAP)
            fill_c = EP_ORANGE if ep_item.get("aired") else EP_GRAY_BG
            text_c = WHITE if ep_item.get("aired") else EP_GRAY_TEXT
            d.rounded_rectangle([cx, cy, cx + CELL - 1, cy + CELL - 1], radius=5, fill=fill_c)
            ep_num = str(ep_item.get("ep", ""))
            ew = text_width(ep_num, F["ep_cell"])
            eh = text_line_height(F["ep_cell"])
            d.text((cx + (CELL - ew) // 2, cy + (CELL - eh) // 2), ep_num,
                   font=F["ep_cell"], fill=text_c)
        cur_ly += ep_block_h + 12

    # Rating histogram block
    if hist_block_h and has_hist:
        _draw_rounded_rect(d, (lx, cur_ly, lx + LEFT_W, cur_ly + hist_block_h), radius=10,
                           fill=WHITE, outline=BORDER, width=1)
        ct_txt = "评分分布"
        ct_w = text_width(ct_txt, F["chart_title"])
        d.text((lx + (LEFT_W - ct_w) // 2, cur_ly + 12), ct_txt,
               font=F["chart_title"], fill=TEXT_LIGHT)

        bars_top = cur_ly + 12 + text_line_height(F["chart_title"]) + 4
        BAR_AREA_W = LEFT_W - 16
        BAR_GAP = 2
        bar_w = (BAR_AREA_W - 9 * BAR_GAP) // 10
        BARS_H = 45
        values = [_int_rating_count(rating_count, i) for i in range(1, 11)]
        max_v = max(values) if values else 1
        if max_v == 0:
            max_v = 1
        for i, v in enumerate(values):
            bar_px = max(2, int(v / max_v * BARS_H))
            bx = lx + 8 + i * (bar_w + BAR_GAP)
            by = bars_top + BARS_H - bar_px
            bar_fill = PRIMARY if i >= 6 else HIST_LOW
            d.rounded_rectangle([bx, by, bx + bar_w, bars_top + BARS_H], radius=2, fill=bar_fill)

        line_y = bars_top + BARS_H + 2
        d.line([lx + 8, line_y, lx + LEFT_W - 8, line_y], fill=BORDER)
        lbl_y = line_y + 2
        d.text((lx + 8, lbl_y), "1", font=F["chart_title"], fill=(200, 200, 200))
        mid_x = lx + 8 + 4 * (bar_w + BAR_GAP)
        d.text((mid_x, lbl_y), "5", font=F["chart_title"], fill=(200, 200, 200))
        right_x = lx + 8 + 9 * (bar_w + BAR_GAP)
        d.text((right_x, lbl_y), "10", font=F["chart_title"], fill=(200, 200, 200))

    # ── RIGHT COLUMN ──────────────────────────────────────────────────────────
    rx = CARD_PAD + LEFT_W + COL_GAP
    ry = CARD_PAD + 16

    # Title
    for line in title_lines:
        d.text((rx, ry), line, font=F["title"], fill=TEXT_MAIN)
        ry += LH_title

    # Subtitle
    if subtitle:
        d.text((rx, ry), subtitle[:60], font=F["subtitle"], fill=TEXT_SUB)
        ry += subtitle_block_h

    ry += 16  # gap

    # Score row
    if score is not None:
        cur_rx = rx
        # Star + score value
        d.text((cur_rx, ry + 8), "★", font=F["star"], fill=PRIMARY)
        cur_rx += text_width("★", F["star"]) + 4
        score_str = str(score)
        d.text((cur_rx, ry), score_str, font=F["score"], fill=PRIMARY)
        cur_rx += text_width(score_str, F["score"]) + 12

        # Rank badge
        if rank:
            rank_txt = f"#{rank}"
            rank_tw = text_width(rank_txt, F["rank_tag"]) + 20
            _draw_rounded_rect(d, (cur_rx, ry + 8, cur_rx + rank_tw, ry + 34),
                               radius=8, fill=SECONDARY_BG)
            d.text((cur_rx + 10, ry + 10), rank_txt, font=F["rank_tag"], fill=ORANGE_DEEP)
            cur_rx += rank_tw + 12

        # Vote count
        if rating_total:
            ct = f"{rating_total} 人评分"
            ct_w = text_width(ct, F["count"]) + 20
            _draw_rounded_rect(d, (cur_rx, ry + 10, cur_rx + ct_w, ry + 34),
                               radius=16, fill=(245, 245, 245))
            d.text((cur_rx + 10, ry + 12), ct, font=F["count"], fill=TEXT_SUB)
            cur_rx += ct_w + 8

        # Watching count
        if collection_doing:
            dt = f"{collection_doing} 人在看"
            dt_w = text_width(dt, F["count"]) + 20
            _draw_rounded_rect(d, (cur_rx, ry + 10, cur_rx + dt_w, ry + 34),
                               radius=16, fill=(245, 245, 245))
            d.text((cur_rx + 10, ry + 12), dt, font=F["count"], fill=TEXT_SUB)

        ry += score_row_h + 16
    else:
        d.text((rx, ry), "暂无评分", font=F["subtitle"], fill=TEXT_LIGHT)
        ry += score_row_h + 16

    # Tags
    if tags:
        tag_x, tag_y = rx, ry
        for tag_txt in tags:
            tw = text_width(tag_txt, F["tag"]) + 24
            if tag_x + tw > rx + RIGHT_W:
                tag_x = rx
                tag_y += tag_row_h + 8
            _draw_rounded_rect(d, (tag_x, tag_y, tag_x + tw, tag_y + tag_row_h),
                               radius=6, fill=WHITE, outline=(220, 220, 220), width=1)
            d.text((tag_x + 12, tag_y + 4), tag_txt, font=F["tag"], fill=TEXT_SUB)
            tag_x += tw + 8
        ry = tag_y + tag_row_h + 16

    # Summary section (with dashed separator)
    ry += 20  # space for dashed line
    dash_y = ry - 12
    for dx in range(rx, rx + RIGHT_W, 12):
        d.line([dx, dash_y, min(dx + 6, rx + RIGHT_W), dash_y], fill=(220, 220, 220))
    d.text((rx, ry), "简介", font=F["sum_label"], fill=(*TEXT_MAIN, 200))
    ry += text_line_height(F["sum_label"]) + 8

    for line in summary_lines:
        d.text((rx, ry), line, font=F["summary"], fill=(74, 74, 74))
        ry += LH_sum

    # Footer (pinned to bottom of card)
    footer_y = card_h - CARD_PAD - 30
    d.line([rx, footer_y, rx + RIGHT_W, footer_y], fill=BORDER)
    fi_x = rx

    if date_str:
        d.text((fi_x, footer_y + 8), date_str, font=F["footer"], fill=TEXT_LIGHT)
        fi_x += text_width(date_str, F["footer"]) + 20

    if platform:
        d.text((fi_x, footer_y + 8), platform, font=F["footer"], fill=TEXT_LIGHT)

    if subject_id is not None:
        id_txt = f"ID: {subject_id}"
        id_w = text_width(id_txt, F["footer"])
        d.text((rx + RIGHT_W - id_w, footer_y + 8), id_txt, font=F["footer"], fill=(200, 200, 200))

    # ── Weekday badge ─────────────────────────────────────────────────────────
    if air_weekday:
        BADGE_W, BADGE_H = 56, 48
        badge = Image.new("RGBA", (BADGE_W, BADGE_H), (0, 0, 0, 0))
        bd = ImageDraw.Draw(badge)
        bd.polygon(
            [(0, 0), (BADGE_W, 0), (BADGE_W, BADGE_H), (0, BADGE_H)],
            fill=(*EP_ORANGE, 255),
        )
        wk_w = text_width(air_weekday, F["weekday"])
        bd.text(((BADGE_W - wk_w) // 2, 6), air_weekday, font=F["weekday"], fill=WHITE)
        sub_txt = "曜日"
        sub_w = text_width(sub_txt, F["weekday_sub"])
        bd.text(((BADGE_W - sub_w) // 2, 30), sub_txt, font=F["weekday_sub"],
                fill=(255, 255, 255, 210))
        # Rounded left-bottom corner only: clip top-right corner of card
        card.alpha_composite(badge, (CARD_W - BADGE_W, 0))

    # ── Composite with shadow ─────────────────────────────────────────────────
    shadow = _make_shadow(CARD_W, card_h)
    result = shadow
    result.alpha_composite(card, (SHADOW_PAD, SHADOW_PAD))

    return image_to_base64(result)
