"""美团 CPS 券本地缓存表(meituan_coupon)。

把美团联盟 CPS 的券定时抓进本地库,供「销量 / 佣金排序」等查询**直接从库里捞、本地排序**,
不再每次实时打美团接口——美团搜索/供给对销量排序支持差(实测乱序)、且有 402 限流和召回上限。

数据来源(source,北京试点):
  - search_waimai : 到家/外卖, 搜索关键词「外卖」, 翻到尽头
  - search_meishi : 到家/外卖, 搜索关键词「美食」, 翻到尽头
  - store_supply  : 到店, 多业务线供给(到餐+到综+酒店+门票), 翻到尽头

每 5min(本地测试)/ 1h(线上)全量抓一次,按 (source, product_view_sign) upsert 存最新态,
last_seen 每轮刷新(可据此清理长期未再出现的陈旧券)。

去重说明:`product_view_sign` / `sku_view_id` 都是「按召回渠道生成」的,跨渠道(搜索 vs 供给)
会变,**不能当商品全局唯一 id**。因此:
  - 存储:按 (source, product_view_sign) upsert(同源短周期内 sign 稳定)。
  - 查询:用 `dedup_key = md5(brand|name|price)` 跨源去重,再按 `sale_volume_num` / `commission_percent` 排序。

价格统一存「分」(cents),与 price_report / comparison_record 一致。`raw` 保留整条原始返回
(字段越详细越好,避免后续漏字段还要重抓)。
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import JSON, DateTime, Float, Integer, String, UniqueConstraint, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column

from app.db.base import Base


class MeituanCoupon(Base):
    __tablename__ = "meituan_coupon"
    __table_args__ = (
        UniqueConstraint("source", "product_view_sign", name="uq_meituan_coupon_source_sign"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)

    # ===== 来源 / 分类 =====
    # source: search_waimai | search_meishi | store_supply
    source: Mapped[str] = mapped_column(String(16), index=True, nullable=False)
    platform: Mapped[int] = mapped_column(Integer, nullable=False)              # 1 到家/外卖, 2 到店
    biz_line: Mapped[int | None] = mapped_column(Integer, nullable=True)        # 到店: 1到餐 2到综 3酒店 4门票
    city_id: Mapped[str] = mapped_column(String(32), index=True, nullable=False)

    # ===== 召回 id(按渠道生成,跨渠道会变;product_view_sign 用于换推广链) =====
    product_view_sign: Mapped[str] = mapped_column(String(128), nullable=False)
    sku_view_id: Mapped[str | None] = mapped_column(String(128), nullable=True)

    # ===== 商品本体 =====
    name: Mapped[str | None] = mapped_column(String(256), nullable=True)
    brand_name: Mapped[str | None] = mapped_column(String(128), index=True, nullable=True)
    sell_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)
    original_price_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)
    head_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
    # 头图字节大小 / MIME 类型:采集时 HEAD head_url 得到(Content-Length / Content-Type)。
    # 读取侧已统一压缩(不再按大小判定),这两列保留用于分析/监控(GIF 占比、体积分布等)。
    image_size: Mapped[int | None] = mapped_column(Integer, nullable=True)         # 字节
    image_type: Mapped[str | None] = mapped_column(String(32), nullable=True)      # 如 image/jpeg

    # ===== 销量(美团只给粗档位:热销1w+;num=排序用的下界数值,如 1w+ → 10000) =====
    sale_volume: Mapped[str | None] = mapped_column(String(32), nullable=True)
    sale_volume_num: Mapped[int | None] = mapped_column(Integer, index=True, nullable=True)

    # ===== 佣金(percent 如 1.4 表示 1.4%;amount 为分) =====
    commission_percent: Mapped[float | None] = mapped_column(Float, index=True, nullable=True)
    commission_amount_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)

    # ===== 门店 / 距离 =====
    poi_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
    available_poi_num: Mapped[int | None] = mapped_column(Integer, nullable=True)
    delivery_distance_m: Mapped[float | None] = mapped_column(Float, nullable=True)

    # ===== 跨源去重键 + 原始返回 =====
    dedup_key: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
    raw: Mapped[dict] = mapped_column(
        JSON().with_variant(JSONB(), "postgresql"), nullable=False, default=dict
    )

    # ===== 抓取时间窗 =====
    first_seen: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )
    last_seen: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
    )

    def __repr__(self) -> str:  # pragma: no cover
        return (
            f"<MeituanCoupon id={self.id} source={self.source} "
            f"name={self.name!r} sale={self.sale_volume} comm={self.commission_percent}>"
        )
