"""
AI 评图分 — E12 飞轮·展示层（倩倩姐2026-06-16拍板）

红线（不可违反）：
- 只做"生成时筛选 + 落库 + 前端展示 + 高分优先排序"。
- 绝不进飞轮权重：飞轮权重只认真实信号（选了哪张/改了什么/过审与否）。
- eval_score 全程留 NULL；AI 分单独存 ai_visual_score，不复用 eval_score。
- 评分失败绝不阻断生图：返回 None，图照常入库展示。

vision 走 GPT 现有通道最强档（claude-opus-4-8），不补 GEMINI_KEY。
"""
from __future__ import annotations
import json
import logging
import re
from typing import Any

logger = logging.getLogger(__name__)

# 评图维度=纯视觉质量（不碰文字对错——那条路线已撤，见 image_gen.py 注释）
VISION_SCORE_PROMPT = (
    "你是小红书资深视觉运营，给下面这张种草配图打分。只看视觉质量，不纠结文字错别字。\n"
    "评分维度（综合给一个 0-100 总分）：\n"
    "1. 构图与美感：主体突出、留白舒服、色调高级。\n"
    "2. 清晰度：画面锐利不糊、无明显畸变。\n"
    "3. 小红书种草感：像真实博主拍的好图，有质感、有氛围。\n"
    "4. 去AI感：不假、不塑料、不像廉价 AI 渲染图。\n"
    "5. 文字排版观感：标题贴纸排版是否清爽（只看排版美观，不判错别字）。\n"
    "打分基准：80=可直接交付的好图，60=能用但平庸，40以下=明显有问题。\n"
    '只返回 JSON，不要任何多余文字：{"score": <0-100整数>, "note": "<20字内一句话点评>"}'
)


def _parse_score(raw: str) -> dict[str, Any] | None:
    """容错解析模型返回的 JSON（剥 markdown fence / 抓第一个 {...}）。"""
    if not raw:
        return None
    text = raw.strip()
    text = re.sub(r"^```(?:json)?\s*|\s*```$", "", text, flags=re.IGNORECASE).strip()
    try:
        obj = json.loads(text)
    except Exception:
        m = re.search(r"\{.*\}", text, re.DOTALL)
        if not m:
            return None
        try:
            obj = json.loads(m.group(0))
        except Exception:
            return None
    if not isinstance(obj, dict) or "score" not in obj:
        return None
    try:
        score = float(obj["score"])
    except Exception:
        return None
    score = max(0.0, min(100.0, score))   # 钳到 0-100
    note = str(obj.get("note") or "").strip()[:200]
    return {"score": score, "note": note}


async def score_image(client: Any, image_bytes: bytes) -> dict[str, Any] | None:
    """
    给单张图打视觉分。返回 {"score": float, "note": str} 或 None（失败/不可解析）。
    绝不抛异常打断生图链路——任何异常都吞掉返回 None。
    """
    if not image_bytes:
        return None
    try:
        raw = await client.gpt_vision_analyze(VISION_SCORE_PROMPT, [image_bytes])
    except Exception as exc:
        logger.warning("AI 评图调用失败（不阻断生图）：%s", exc)
        return None
    result = _parse_score(raw)
    if result is None:
        logger.warning("AI 评图返回无法解析（不阻断生图）：%s", (raw or "")[:120])
    return result
