"""广告收益报表 schemas。

**逐条广告事件**只读报表:每行一次广告(激励视频展示+发奖按会话合并;信息流展示/发奖各自成行),
含 展示条数、收益(元)、应发/实发金币、对账。字段 snake_case;收益按元(float),金币按整数。
"""
from __future__ import annotations

from datetime import datetime

from pydantic import BaseModel, Field


class AdRevenueImpression(BaseModel):
    """聚合行下钻的单条**展示**明细(每次广告展示一条,展开该组时展示)。"""

    id: int = Field(..., description="ad_ecpm_record 主键")
    created_at: datetime
    ecpm: str = Field(..., description="本次展示 eCPM 原始值(分/千次展示)")
    revenue_yuan: float = Field(..., description="本次展示预估收益(元)= eCPM元 ÷ 1000")
    adn: str | None = Field(None, description="实际填充 ADN 子渠道(pangle/gdt…)")
    slot_id: str | None = Field(None, description="底层 mediation rit(非我们配置的广告位 ID)")


class AdRevenueRecord(BaseModel):
    """聚合行下钻的单条发奖复算明细(与金币审计同源,展开该组时展示)。"""

    record_id: int
    created_at: datetime
    status: str = Field(..., description="granted / capped / ecpm_missing")
    ecpm: str | None = Field(None, description="本次采用的 eCPM 原始值(分/千次展示)")
    ecpm_factor: float | None = Field(None, description="因子1(eCPM 档);非 granted 为空")
    units: int = Field(..., description="折算份数:激励视频恒 1;信息流 = 满 10 秒份数")
    lt_index_start: int | None = Field(None, description="本条占用「账号累计第几份」的起")
    lt_index_end: int | None = Field(None, description="本条占用「账号累计第几份」的止;激励视频 = 起")
    lt_factor_start: float | None = Field(None, description="因子2(LT)起值")
    lt_factor_end: float | None = Field(None, description="因子2(LT)止值;激励视频 = 起")
    expected_coin: int = Field(..., description="按公式复算应发金币")
    actual_coin: int = Field(..., description="实际入账金币")
    matched: bool = Field(..., description="复算与实发是否一致")


class AdRevenueDaily(BaseModel):
    """按日期汇总的一天(供前端按天趋势图;全量,不受分页影响)。"""

    date: str = Field(..., description="北京时间 YYYY-MM-DD")
    impressions: int = Field(..., description="当天展示条数合计")
    revenue_yuan: float = Field(..., description="当天预估收益合计(元)")
    expected_coin: int = Field(..., description="当天应发金币合计")
    actual_coin: int = Field(..., description="当天实发金币合计")


class AdRevenueHourly(BaseModel):
    """按北京小时(0–23)汇总的一小时(供前端按小时趋势图;全量,不受分页影响,单日 granularity=hour 时非空)。"""

    hour: int = Field(..., description="北京时间小时 0–23")
    impressions: int = Field(..., description="该小时展示条数合计")
    revenue_yuan: float = Field(..., description="该小时预估收益合计(元)")
    expected_coin: int = Field(..., description="该小时应发金币合计")
    actual_coin: int = Field(..., description="该小时实发金币合计")


class AdRevenueTypeStat(BaseModel):
    """按广告类型(ad_type)的小计:展示条数 + 预估收益(eCPM 由前端用 收益÷展示×1000 算)。"""

    impressions: int = Field(..., description="该类型展示条数合计")
    revenue_yuan: float = Field(..., description="该类型预估收益合计(元)")


class AdRevenueRow(BaseModel):
    """一次广告事件(逐条一行):激励视频展示与发奖按 ad_session_id 合并;信息流展示 / 发奖各自成行。"""

    event_key: str = Field(..., description="该事件稳定唯一键(imp-{ecpm_id} / rwd-{reward_id});前端 rowKey 用")
    report_date: str = Field(..., description="该事件所属日期(北京时间 YYYY-MM-DD)")
    user_id: int
    user_phone: str | None = Field(None, description="用户手机号(admin 展示用,完整;用户已删 / 查不到为空)")
    ad_type: str = Field(..., description="reward_video(激励视频) / feed(信息流) / draw(Draw 信息流);历史 NULL 视为 feed")
    feed_scene: str | None = Field(
        None,
        description="点位场景:comparison(比价) / coupon(领券) / welfare(福利);供区分比价/领券 Draw 收益;"
        "激励视频与旧数据为空",
    )
    app_env: str | None = Field(None, description="我们的应用:prod(傻瓜比价正式) / test(测试应用);旧数据为空")
    our_code_id: str | None = Field(None, description="我们后台配置的代码位 ID(104xxx);旧数据为空")
    hour: int | None = Field(None, description="北京时间小时 0–23(granularity=hour 时有值;按天为 null)")
    created_at: datetime = Field(..., description="事件时间(有展示=展示时间,纯发奖=发奖时间)")
    # ── 展示侧 ──
    has_impression: bool = Field(..., description="是否有广告展示(信息流逐条展示=True,纯发奖行=False)")
    impressions: int = Field(..., description="本行展示条数:有展示=1 / 纯发奖=0(供日汇总、趋势图复用)")
    ecpm: str | None = Field(None, description="eCPM 原始值(分/千次);展示行取展示值,纯发奖行取发奖采用值")
    revenue_yuan: float = Field(..., description="本次展示预估收益(元)= eCPM元 ÷ 1000;纯发奖行=0")
    adn: str | None = Field(None, description="实际填充 ADN 子渠道(pangle/gdt…);纯发奖行为空")
    slot_id: str | None = Field(None, description="底层 mediation rit(非我们配置的广告位 ID);纯发奖行为空")
    # ── 发奖侧 ──
    has_reward: bool = Field(..., description="是否有发奖记录(激励视频合并行 / 信息流整场发奖行=True;纯展示=False)")
    status: str | None = Field(None, description="发奖状态 granted/closed_early/too_short/…;纯展示为空")
    expected_coin: int = Field(..., description="应发金币(公式复算,与金币审计同源);纯展示=0")
    actual_coin: int = Field(..., description="实发金币(实际入账);纯展示=0")
    matched: bool = Field(..., description="本条应发==实发;纯展示恒 True(不计对账)")
    reward_detail: AdRevenueRecord | None = Field(
        None,
        description="发奖复算明细(eCPM/因子1/份数/LT/因子2/应发/实发/一致);点行展开下钻用,纯展示为空",
    )


class AdRevenueReportOut(BaseModel):
    """报表响应:全量统计 + 按天趋势 + 聚合明细。"""

    date_from: str = Field(..., description="报表起始日期(北京时间 YYYY-MM-DD)")
    date_to: str = Field(..., description="报表结束日期(北京时间 YYYY-MM-DD,闭区间;单日时与 date_from 相同)")
    daily: list[AdRevenueDaily] = Field(..., description="按日期汇总序列(全量,供按天趋势图)")
    hourly: list[AdRevenueHourly] = Field(
        default_factory=list,
        description="按小时汇总序列(全量,供按小时趋势图;按天查询时为空)",
    )
    type_stats: dict[str, AdRevenueTypeStat] = Field(
        default_factory=dict,
        description="按广告类型(ad_type)小计 {ad_type: {impressions, revenue_yuan}};前端取 draw / reward_video 做分类大盘",
    )
    dau: int | None = Field(
        None,
        description="今日活跃用户数(复用大盘口径,last_login_at);**仅查询=今日单天时有值**,历史/多天为 null",
    )
    total: int = Field(..., description="广告事件总数(全量,不受分页影响;= 当前筛选下的分页总条数)")
    truncated: bool = Field(..., description="当前页之后是否还有更多事件(len(events) > offset + limit)")
    total_impressions: int = Field(..., description="全量展示条数合计")
    total_revenue_yuan: float = Field(..., description="全量收益合计(元)")
    total_expected_coin: int = Field(..., description="全量应发金币合计")
    total_actual_coin: int = Field(..., description="全量实发金币合计")
    mismatch_count: int = Field(..., description="应发≠实发的发奖条数(=0 说明全部按公式发放)")
    items: list[AdRevenueRow] = Field(..., description="逐条广告事件(按 日期→用户→时间 排序)")
