"""POST /api/v1/track-price —— 价保哨兵的价格查询接口(LLM mock 实现)。

## 场景

用户在 App 内告知"我在 X 平台 ¥XXX 买了 Y 商品",客户端每天后台调一次此接口
查"该商品当前在该平台的合理价"。客户端按 `savings > 0` 弹"该申请价保了"通知。

## 为什么是 mock + LLM

占坑期没有真实平台价 API。爬虫法规风险大。OCR 用户主动截图比较被动 — 不适合
"每天自动监控"场景。

折中方案:LLM 估"该商品在该平台的日常常见价"(锚价),再用哈希抖动模拟"今天降价
/ 持平 / 涨价"。抖动是**确定性的**(seed = title+platform+date),同一商品同一天
结果一致,跨天才会变化,不会让用户看到"通知反复抖动"。

未来切真 API 时客户端 0 改动(接口签名/响应固定)。

## 抖动设计

seed = sha256(title + platform + YYYY-MM-DD).hexdigest()[:8]
h = int(seed, 16) % 100

- h ∈ [0, 60)  → 降价 3-8%, trend="down" (60% 概率,让通知活跃)
- h ∈ [60, 90) → 持平 ±2%,  trend="flat"
- h ∈ [90, 100)→ 涨价 3-8%, trend="up"

60% 降价概率是策略选择:demo 体验里用户经常能看到"该申请价保了"通知,
功能"动"得多。真实降价频率比这低很多,但 mock 阶段优先体验。
"""
from __future__ import annotations

import hashlib
import json
import logging
import re
import time
from datetime import datetime, timezone
from typing import Optional

from fastapi import APIRouter, HTTPException

from app.llm_client import MOCK_LLM, chat
from app.schemas import TrackPriceRequest, TrackPriceResponse

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

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


SYSTEM_PROMPT = """你是一个商品当前价格估算助手。

# 输入
用户会告诉你:
- 平台: 例如 京东 / 淘宝 / 拼多多 / 抖音
- 商品标题: 商品完整名称
- 用户购买价: 参考用,不一定是当前价
- 购买距今天数: 例如 7天

# 任务

估算该商品在该平台目前的**日常常见售价**(不含特殊大促),作为"基础价"输出。

# 估算思路

- 考虑该平台的常态价位区间(例:京东自营常比拼多多百亿补贴高 5-10%)
- 考虑时间维度:新品 3 个月内可能略涨,旧品逐渐降
- 考虑大促节奏:618 / 双11 前后日常价会下浮
- 不熟悉的商品 → 根据品类 + 品牌常识估算合理日常价
- 完全陌生 → 取用户购买价的 ±5% 区间合理值

# 输出格式

严格 JSON,无 markdown,无解释,无任何其他文字:

{"base_price": 数字, "reasoning": "一句话"}

字段:
- base_price: 商品在该平台的日常常见价数字,**必须正数,不允许 null**
- reasoning: 一句话理由(给排查 LLM 行为用,客户端不展示)
"""


def _parse_llm_output(s: str) -> tuple[Optional[float], str]:
    """从 LLM 输出剥出 base_price + reasoning。返回 (base_price | None, reasoning)。"""
    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, ""
        try:
            data = json.loads(m.group(0))
        except json.JSONDecodeError:
            return None, ""
    if not isinstance(data, dict):
        return None, ""

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

    reasoning = data.get("reasoning")
    if not isinstance(reasoning, str):
        reasoning = ""

    return base_price, reasoning


def _compute_jitter(title: str, platform: str, today_str: str) -> tuple[float, str]:
    """确定性抖动 — 同 title+platform+date 结果一致,跨天才变。

    返回 (factor, trend),factor 是乘到 base_price 上的系数。
    """
    seed_str = f"{title}|{platform}|{today_str}"
    digest = hashlib.sha256(seed_str.encode("utf-8")).hexdigest()
    h = int(digest[:8], 16) % 100

    if h < 60:
        # 降价 3-8%。在 [0, 60) 内线性插值到 [0.92, 0.97]
        factor = 0.92 + (h / 60.0) * 0.05
        trend = "down"
    elif h < 90:
        # 持平 ±2%。在 [60, 90) 内线性插值到 [0.98, 1.02]
        factor = 0.98 + ((h - 60) / 30.0) * 0.04
        trend = "flat"
    else:
        # 涨价 3-8%。在 [90, 100) 内线性插值到 [1.03, 1.08]
        factor = 1.03 + ((h - 90) / 10.0) * 0.05
        trend = "up"

    return factor, trend


@router.post("/track-price", response_model=TrackPriceResponse)
def track_price(req: TrackPriceRequest) -> TrackPriceResponse:
    title = req.product_title.strip()
    platform = req.platform.strip()
    if not title or not platform:
        raise HTTPException(status_code=422, detail="empty_title_or_platform")
    if req.purchase_price <= 0:
        raise HTTPException(status_code=422, detail="invalid_purchase_price")

    # 计算"购买距今多少天",给 LLM 当上下文
    now_ts = int(time.time())
    days_since = max(0, (now_ts - req.purchase_at) // 86400)

    user_msg = (
        f"平台: {platform}\n"
        f"商品标题: {title}\n"
        f"用户购买价: ¥{req.purchase_price:.2f}\n"
        f"购买距今: {days_since}天"
    )
    if MOCK_LLM:
        # mock:跳过 LLM,下方 base_price 回退到 purchase_price 锚 + 确定性抖动。
        raw = ""
    else:
        try:
            raw = chat(
                messages=[
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": user_msg},
                ]
            )
            logger.debug("track-price LLM raw: %s", raw[:300])
        except Exception as e:
            # LLM 不可用(限流/网络/超时):不 500,用 purchase_price 作锚。
            logger.warning("track-price LLM call failed, using purchase_price anchor: %s", e)
            raw = ""

    base_price, reasoning = _parse_llm_output(raw)

    # base_price 兜底:LLM 不听话或给 <=0,用 purchase_price 作为锚
    # (occurence 极少;占坑期 mock 不能让接口因 LLM 抽风就 500)
    if base_price is None or base_price <= 0:
        logger.warning(
            "LLM did not return valid base_price for %r, fallback to purchase_price",
            title,
        )
        base_price = req.purchase_price

    # 抖动 — seed = title + platform + 服务器当前日期(UTC,日切清晰)
    today_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    factor, trend = _compute_jitter(title, platform, today_str)
    current_price = round(base_price * factor, 2)

    savings = round(req.purchase_price - current_price, 2)

    logger.info(
        "track-price: platform=%s title=%r purchase=¥%.2f base=¥%.2f current=¥%.2f"
        " trend=%s savings=¥%.2f reasoning=%r",
        platform,
        title,
        req.purchase_price,
        base_price,
        current_price,
        trend,
        savings,
        reasoning[:80],
    )

    return TrackPriceResponse(
        current_price=current_price,
        base_price=base_price,
        trend=trend,
        savings=savings,
        checked_at=now_ts,
    )
