"""POST /api/v1/quick-quote —— Android 端主动比价查询接口。

application/json:
  {"title": "iPhone 15 Pro Max 1TB", "clusters": [{"id":1,"title":"..."}, ...]}

使用场景:用户在 App 内"快速比价"输入框输入商品名查询 — 不需要先去
购物 App 浮窗记账,直接得到"市场常见价 + 是否命中已记过的同款"。

返回:
  {"title": "...", "typical_price": 13999.0, "cluster_id": 1 | null}

LLM 行为:
- title 标准化 (修正错别字 / 统一规格表达)
- typical_price 必须给数字 (与 /parse 一致的兜底逻辑)
- cluster_id 按现有 簇匹配规则 判断
"""
from __future__ import annotations

import hashlib
import json
import logging
import re
from typing import Optional

from fastapi import APIRouter, HTTPException

from app.llm_client import MOCK_LLM, chat
from app.schemas import ClusterDto, QuickQuoteRequest, QuickQuoteResponse

logger = logging.getLogger("shagua.quick_quote")

router = APIRouter(prefix="/api/v1")


SYSTEM_PROMPT = """你是商品归簇与市场参考价估算助手。

# 任务

用户会发给你两部分输入:
1. 一个商品标题字符串
2. 用户已有的"商品簇"列表(每个簇用一个代表标题描述)

请你完成两件事:
A. 估算该商品的「市场常见价」(typical_price):主流电商平台常见售价区间
   的中位值,不含双 11 / 618 等特殊促销价
B. 判断该商品是否归属于已有簇中的某一个,若是返回该簇 id,若否返回 null

# 簇匹配规则

两件商品视为「同一簇」,核心商品名一致即可,无视规格、颜色、容量、装数、
性别、码数等差异。

例:
- "iPhone 15 Pro 256GB" ↔ "iPhone 15 Pro 1TB 暮光紫"  → 同簇 ✓
- "iPhone 15 Pro" ↔ "iPhone 14 Pro"                   → 不同簇 ✗ (型号不同)
- "海尔保温杯 500ml" ↔ "九阳保温杯 500ml"            → 不同簇 ✗ (品牌不同)

# 市场常见价估算

- 取主流电商(淘宝/京东/拼多多/抖音电商)常见售价区间的中位值
- 不含双 11、618、年货节、品牌大促等特殊促销价
- 单位:元,可以有小数,必须为正数
- **必须给出一个数字,不允许 null**。即使你不熟悉,也要根据品类、品牌、
  规格的常识给出合理估算
- 估算思路:
  * 知名品牌 → 该品牌该品类的官方建议零售价或主流电商常见价
  * 冷门品牌 → 同品类的市场常见价区间中位值
  * 完全陌生 → 给出符合常识的合理估值

# 输出格式

严格 JSON,无任何额外文字、不要 markdown 代码块、不要解释:

{"title": "标准化后的商品标题", "typical_price": 99.9, "cluster_id": 3}

字段说明:
- title:整理后的标题(可纠错 / 统一规格写法,但保留原意)
- typical_price:市场常见价数字(正数),**必须返回数字,不允许 null**
- cluster_id:命中已有簇返回其 id (整数);未命中返回 null
"""


def _format_clusters(clusters: list[ClusterDto]) -> str:
    if not clusters:
        return "(无,这是用户记录的第一件商品)"
    return "\n".join(f"- id={c.id}: {c.title}" for c in clusters)


def _mock_raw(title: str) -> str:
    """占坑期 mock(DUOBIBI_MOCK_LLM):基于标题确定性造市场常见价,不归簇(由客户端新建)。"""
    seed = int(hashlib.sha256(title.encode("utf-8")).hexdigest()[:8], 16)
    typical = round(49 + seed % 9950 + (seed % 100) / 100.0, 2)
    return json.dumps({"title": title, "typical_price": typical, "cluster_id": None}, ensure_ascii=False)


def _parse_llm_output(s: str) -> tuple[Optional[str], Optional[float], Optional[int]]:
    s = s.strip()
    s = re.sub(r"^```(?:json)?\s*", "", s)
    s = re.sub(r"\s*```$", "", s)
    try:
        data = json.loads(s)
    except json.JSONDecodeError:
        m = re.search(r"\{[^{}]*\}", s)
        if not m:
            return None, None, None
        try:
            data = json.loads(m.group(0))
        except json.JSONDecodeError:
            return None, None, None
    if not isinstance(data, dict):
        return None, None, None

    title = data.get("title")
    title = title.strip() if isinstance(title, str) and title.strip() else None

    tp_raw = data.get("typical_price")
    if isinstance(tp_raw, bool):
        typical_price = None
    elif isinstance(tp_raw, (int, float)):
        typical_price = float(tp_raw)
    elif isinstance(tp_raw, str):
        try:
            typical_price = float(tp_raw.strip())
        except ValueError:
            typical_price = None
    else:
        typical_price = None

    cid_raw = data.get("cluster_id")
    if isinstance(cid_raw, bool):
        cluster_id = None
    elif isinstance(cid_raw, int):
        cluster_id = cid_raw
    elif isinstance(cid_raw, float) and cid_raw.is_integer():
        cluster_id = int(cid_raw)
    else:
        cluster_id = None

    return title, typical_price, cluster_id


@router.post("/quick-quote", response_model=QuickQuoteResponse)
def quick_quote(req: QuickQuoteRequest) -> QuickQuoteResponse:
    title_in = req.title.strip()
    if not title_in:
        raise HTTPException(status_code=422, detail="empty_title")

    user_msg = (
        f"商品标题: {title_in}\n\n"
        f"已有商品簇:\n{_format_clusters(req.clusters)}"
    )
    if MOCK_LLM:
        raw = _mock_raw(title_in)
    else:
        try:
            raw = chat(
                messages=[
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": user_msg},
                ]
            )
            logger.debug("quick-quote LLM raw: %s", raw[:300])
        except Exception as e:
            # LLM 不可用:不 500,下方 typical_price 走兜底。
            logger.warning("quick-quote LLM call failed: %s", e)
            raw = ""

    title_out, typical_price, cluster_id = _parse_llm_output(raw)

    # title 兜底:LLM 没整理就用原值
    if title_out is None:
        title_out = title_in

    # cluster_id 校验:防 LLM 编造
    valid_ids = {c.id for c in req.clusters}
    if cluster_id is not None and cluster_id not in valid_ids:
        logger.warning("LLM returned unknown cluster_id=%s, treating as null", cluster_id)
        cluster_id = None

    # typical_price 兜底:LLM 不听话或给 <=0,用一个保守占位(实际极少触发)
    if typical_price is None or typical_price <= 0:
        logger.warning("LLM did not return valid typical_price, fallback to 0.0")
        typical_price = 0.0

    return QuickQuoteResponse(
        title=title_out,
        typical_price=typical_price,
        cluster_id=cluster_id,
    )
