"""Mock 模式的"提取/归簇/估常见价"实现。

用于占坑期 iOS 端开发与审核演示:不调真实 LLM,直接按图片字节哈希在
预置商品池中轮询返回,稳定可重放,且能自然制造"归簇命中"和"新建簇"两种路径。

接真模型时:
- 把 routes 里调用 mock_extract_image / mock_extract_text 的位置切换到调用真实 extractor
- 或在外层加 USE_MOCK 环境变量分流(本占坑期方案不引入开关,接模型时直接换实现)
"""
from __future__ import annotations

import asyncio
import hashlib
import logging
import random
from typing import Optional

from app.schemas import ClusterDtoStr

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

# 10 件预置商品。挑选原则:覆盖 7 个白名单平台 + 3C/食品/服饰/小家电 多品类,
# 价格分布从 28 元(快餐)到 9999 元(手机)跨度大,便于测试统计页/趋势图视觉效果。
MOCK_PRODUCTS: list[dict] = [
    {"title": "iPhone 15 Pro Max 256GB 暮光紫", "price": 9999.0, "source_app": "淘宝", "typical_price": 13999.0},
    {"title": "海底捞外卖经典套餐 2-3 人餐", "price": 158.0, "source_app": "美团", "typical_price": 199.0},
    {"title": "元气森林白桃味气泡水 480ml*15", "price": 49.9, "source_app": "京东", "typical_price": 65.0},
    {"title": "Nike Air Force 1 三色刺绣男款 41", "price": 599.0, "source_app": "拼多多", "typical_price": 799.0},
    {"title": "海尔不锈钢真空保温杯 500ml", "price": 89.0, "source_app": "京东", "typical_price": 129.0},
    {"title": "麦当劳麦辣鸡腿堡套餐(大份)", "price": 28.0, "source_app": "美团", "typical_price": 35.0},
    {"title": "飞利浦多功能旋转剃须刀 S5586", "price": 469.0, "source_app": "淘宝", "typical_price": 599.0},
    {"title": "Lululemon Align 高腰瑜伽裤 25", "price": 750.0, "source_app": "抖音", "typical_price": 980.0},
    {"title": "蒙牛特仑苏纯牛奶 250ml*16 礼盒", "price": 65.0, "source_app": "京东", "typical_price": 88.0},
    {"title": "戴森 V12 Detect Slim 无线吸尘器", "price": 3699.0, "source_app": "淘宝", "typical_price": 4490.0},
]


# 让"同一张图触发同一个商品"可复现,但同一个 mock 商品在不同 record 里,
# 价格上下浮动 ±5%(模拟用户在不同时间记到的不同到手价),便于看趋势图效果。
def _picked_with_jitter(base: dict, jitter_seed: int) -> dict:
    rnd = random.Random(jitter_seed)
    delta = rnd.uniform(-0.05, 0.05)
    price = round(base["price"] * (1 + delta), 2)
    return {
        "title": base["title"],
        "price": price,
        "source_app": base["source_app"],
        "typical_price": base["typical_price"],
    }


def _pick_by_bytes(blob: bytes) -> dict:
    """字节稳定哈希到预置商品。同样字节 → 同样商品,便于真机重放。"""
    h = hashlib.sha256(blob).digest()
    idx = h[0] % len(MOCK_PRODUCTS)
    # 用第二个字节作为价格抖动种子,让"同一商品"在多次记账时价格略有差异
    jitter_seed = h[1]
    return _picked_with_jitter(MOCK_PRODUCTS[idx], jitter_seed)


def _pick_by_text(title: str, price: float) -> dict:
    """文本场景: 客户端已知 title/price,后端只决定 typical_price。
    根据 title 哈希挑一个 mock 商品的 typical_price 作为参考价。"""
    h = hashlib.sha256(title.encode("utf-8")).digest()
    idx = h[0] % len(MOCK_PRODUCTS)
    return MOCK_PRODUCTS[idx]


def _match_cluster(picked_title: str, clusters: list[ClusterDtoStr]) -> Optional[str]:
    """简易归簇: 取 picked title 前 4 个字符,在 clusters 里找包含该子串的;
    找到就返回该 cluster.id (字符串)。

    这不是真实 LLM 的语义归簇能力,只是为了让客户端在测试中能稳定触发"归簇命中"路径:
    用户第一次记某商品 → 新建簇;再次记同 hash 的图片 → 命中已有簇。
    """
    if not clusters:
        return None
    needle = picked_title[:4] if len(picked_title) >= 4 else picked_title
    for c in clusters:
        if needle and needle in c.title:
            return c.id
    return None


async def _simulate_latency() -> None:
    """模拟真实 LLM 延迟 1.5-2.5 秒,让 iOS 端 loading UI 测起来真实。

    **必须用 asyncio.sleep 而非 time.sleep**:本模块被 async 路由 (parse_image) 调用,
    同步 sleep 会阻塞整个 event loop —— 任何一个请求 sleep 期间,server 无法处理
    任何其他请求(健康检查、其他识别请求都会卡)。占坑期 QPS 低也会因为并发审核
    被串行化,严重影响体验。
    """
    await asyncio.sleep(random.uniform(1.5, 2.5))


async def mock_extract_image(image_bytes: bytes, clusters: list[ClusterDtoStr]) -> dict:
    """图片版 mock 提取。返回与真实 extractor 同形状的 dict。

    入参:
      - image_bytes: 客户端上传的截图原始字节(已被客户端降采样)
      - clusters: 客户端发来的已有商品簇列表

    返回(success):
      {success, title, price, source_app, cluster_id, typical_price}
    """
    await _simulate_latency()
    picked = _pick_by_bytes(image_bytes)
    cluster_id = _match_cluster(picked["title"], clusters)

    logger.info(
        "mock image: bytes=%d picked=%r cluster_id=%s",
        len(image_bytes),
        picked["title"],
        cluster_id,
    )
    return {
        "success": True,
        "title": picked["title"],
        "price": picked["price"],
        "source_app": picked["source_app"],
        "cluster_id": cluster_id,
        "typical_price": picked["typical_price"],
    }


async def mock_extract_text(
    title: str,
    price: float,
    source_app: str,
    clusters: list[ClusterDtoStr],
) -> dict:
    """文本版 mock: 客户端已知 title/price/source_app,后端只决定归簇 + 估常见价。"""
    await _simulate_latency()
    picked = _pick_by_text(title, price)
    typical_price = picked["typical_price"]
    cluster_id = _match_cluster(title, clusters)

    logger.info(
        "mock text: title=%r price=%.2f source=%s cluster_id=%s typical=%.2f",
        title,
        price,
        source_app,
        cluster_id,
        typical_price,
    )
    return {
        "success": True,
        "title": title,
        "price": price,
        "source_app": source_app,
        "cluster_id": cluster_id,
        "typical_price": typical_price,
    }


async def mock_extract_ocr(ocr_text: str, clusters: list[ClusterDtoStr]) -> dict:
    """OCR 文本版 mock:按整段 OCR 文本的哈希挑预置商品,稳定可重放。

    与 image / text 版同形状返回,客户端复用同一套结果处理逻辑。
    (此前 main.py 引用了 parse_ocr router 但实现缺失会导致启动 ImportError,这里补齐对应 mock。)
    """
    await _simulate_latency()
    h = hashlib.sha256(ocr_text.encode("utf-8")).digest()
    picked = _picked_with_jitter(MOCK_PRODUCTS[h[0] % len(MOCK_PRODUCTS)], h[1])
    cluster_id = _match_cluster(picked["title"], clusters)

    logger.info(
        "mock ocr: chars=%d picked=%r cluster_id=%s",
        len(ocr_text),
        picked["title"],
        cluster_id,
    )
    return {
        "success": True,
        "title": picked["title"],
        "price": picked["price"],
        "source_app": picked["source_app"],
        "cluster_id": cluster_id,
        "typical_price": picked["typical_price"],
    }
