"""信息流广告奖励 CRUD。

点位 2:每展示满 10 秒累计一份奖励,视频完成后一次性入账。当前一期由客户端在
完成回调后上报;后续若 SDK/S2S 能提供更强确认信号,可继续复用本表的幂等键。
"""
from __future__ import annotations

from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.core import rewards
from app.core.rewards import cn_today
from app.models.ad_feed_reward import AdFeedRewardRecord
from app.repositories import wallet as crud_wallet

FEED_REWARD_UNIT_SECONDS = 10
# 单个 feed 事件的时长上限(秒):一期 duration_seconds 由客户端上报,伪造超长时长会刷份数
# (每 10 秒 1 份)。真实单条信息流视频远小于此;取 120s=12 份封顶,挡刷量、不影响正规单。
# 与 rewards.AD_ECPM_MAX_FEN(eCPM 钳顶)合起来,把单事件可铸金币锁进有限区间。
FEED_MAX_DURATION_SECONDS = 120


def _find_by_event(db: Session, client_event_id: str) -> AdFeedRewardRecord | None:
    return db.execute(
        select(AdFeedRewardRecord).where(
            AdFeedRewardRecord.client_event_id == client_event_id
        )
    ).scalar_one_or_none()


def _granted_today(db: Session, user_id: int, reward_date: str) -> int:
    return db.execute(
        select(func.count())
        .select_from(AdFeedRewardRecord)
        .where(
            AdFeedRewardRecord.user_id == user_id,
            AdFeedRewardRecord.reward_date == reward_date,
            AdFeedRewardRecord.status == "granted",
        )
    ).scalar_one()


def granted_unit_total(db: Session, user_id: int) -> int:
    """账号累计已发奖**条**数(COUNT status=granted),不按天重置 = 因子2(LT)基线。

    口径:**一条广告 = 一份额度(一个单次公式值)**,故"已发条数"即"已发份数"。供前端拉取后精确复刻
    金币公式做实时金币进度条(前端因子2 用 granted_unit_total + 本场已结算条数 + 1 取档,与本表发奖一致)。
    """
    return int(
        db.execute(
            select(func.count()).select_from(AdFeedRewardRecord).where(
                AdFeedRewardRecord.user_id == user_id,
                AdFeedRewardRecord.status == "granted",
            )
        ).scalar_one()
    )


def grant_feed_reward(
    db: Session,
    user_id: int,
    *,
    client_event_id: str,
    ecpm: str,
    duration_seconds: int,
    ad_type: str = "feed",
    ad_session_id: str | None = None,
    adn: str | None = None,
    slot_id: str | None = None,
    feed_scene: str | None = None,
    trace_id: str | None = None,
    app_env: str | None = None,
    our_code_id: str | None = None,
    aborted: bool = False,
    display_coin: int = 0,
) -> AdFeedRewardRecord:
    """**每条**信息流广告(客户端每条各上报一次)结算奖励。client_event_id 幂等,同号重试不重复发。

    发奖规则(所见即所得, 2026-06-27 用户拍板「显示多少给多少」):优先**直接发客户端小球显示的金币
    display_coin**;防刷钳到本条「1 份满额」(eCPM 已钳 AD_ECPM_MAX_FEN, 因子2 按账号累计已发条数取档),
    合法显示(实际因子2 × 进度 p ≤ 1 份)不被砍, 只挡伪造天价值。旧客户端不传 display_coin 时退回
    「看满 10 秒发整份」(兼容不断币)。因子2(LT)由**客户端**按 granted 行 COUNT(拉自 /feed-reward/units)
    算进 display_coin, 后端只记 granted 行让该计数自增, 不再服务端重算份值。
    - aborted=True(用户中途 ✕ 关闭这条):本条不发,记 status='closed_early'。
    - display_coin 为 0 且时长不足一份:记 status='too_short' 不发(不计 LT / 当日上限)。
    - 命中当日条数上限:记 status='capped' 不发。
    duration_seconds 落库留痕(unit_count 字段), 旧端兼容路径据它判是否满 1 份。
    feed_scene:点位场景(comparison/coupon/welfare),仅归类落库,不参与计算。
    ad_type:广告形态(feed 信息流 / draw Draw 信息流),仅归类落库;**每日上限与因子2(LT)仍按本表
    全表 unit 累计(feed+draw 共享同一发奖池/上限),不按 ad_type 拆分**。
    """
    existing = _find_by_event(db, client_event_id)
    if existing is not None:
        return existing

    today = cn_today().isoformat()
    # 客户端上报时长先钳到 FEED_MAX_DURATION_SECONDS,防伪造超长时长刷份数(见常量注释)。
    safe_duration = max(0, min(duration_seconds, FEED_MAX_DURATION_SECONDS))
    unit_count = safe_duration // FEED_REWARD_UNIT_SECONDS

    # 用户中途关闭广告:整场不发(全程不关才发),留一条 closed_early 记录原因。优先级最高。
    if aborted:
        rec = AdFeedRewardRecord(
            client_event_id=client_event_id,
            user_id=user_id,
            reward_date=today,
            duration_seconds=safe_duration,
            unit_count=unit_count,
            ad_session_id=ad_session_id,
            ecpm_raw=ecpm,
            adn=adn,
            slot_id=slot_id,
            ad_type=ad_type,
            feed_scene=feed_scene,
            trace_id=trace_id,
            app_env=app_env,
            our_code_id=our_code_id,
            coin=0,
            status="closed_early",
        )
        return _commit_record(db, rec, client_event_id)

    if _granted_today(db, user_id, today) >= rewards.get_ad_daily_limit(db):
        rec = AdFeedRewardRecord(
            client_event_id=client_event_id,
            user_id=user_id,
            reward_date=today,
            duration_seconds=safe_duration,
            unit_count=unit_count,
            ad_session_id=ad_session_id,
            ecpm_raw=ecpm,
            adn=adn,
            slot_id=slot_id,
            ad_type=ad_type,
            feed_scene=feed_scene,
            trace_id=trace_id,
            app_env=app_env,
            our_code_id=our_code_id,
            coin=0,
            status="capped",
        )
        return _commit_record(db, rec, client_event_id)

    # 所见即所得(用户 2026-06-27「显示多少给多少」): 优先发**客户端小球显示**的金币 display_coin,
    # 钳到本条「1 份满额」防刷(eCPM 已钳 AD_ECPM_MAX_FEN; 合法显示=因子2×p≤1份, 不会被砍)。
    # 因子2(LT)按账号累计已发条数(granted 行 COUNT), 第 existing_ads+1 条。
    existing_ads = granted_unit_total(db, user_id)
    unit_cap = rewards.calculate_ad_reward_coin(ecpm, existing_ads + 1)
    if display_coin > 0:
        coin = min(display_coin, unit_cap)              # 新端: 所见即所得(直接发小球显示金币)
    elif unit_count >= 1:
        coin = unit_cap                                 # 旧端没传 display_coin: 退回「看满 1 份发整份」(兼容)
    else:
        coin = 0

    # 显示金币为 0 且没满一份 → 不发, 记 too_short 留痕(不写 granted 行 → 不计 LT / 当日上限)。
    if coin <= 0:
        rec = AdFeedRewardRecord(
            client_event_id=client_event_id,
            user_id=user_id,
            reward_date=today,
            duration_seconds=safe_duration,
            unit_count=unit_count,
            ad_session_id=ad_session_id,
            ecpm_raw=ecpm,
            adn=adn,
            slot_id=slot_id,
            ad_type=ad_type,
            feed_scene=feed_scene,
            trace_id=trace_id,
            app_env=app_env,
            our_code_id=our_code_id,
            coin=0,
            status="too_short",
        )
        return _commit_record(db, rec, client_event_id)

    # 按点位场景拆流水文案(2026-07):比价等候期看的广告 vs 领券时看的广告,在收益明细里分开显示。
    # feed_scene=comparison→比价奖励 / coupon→领券奖励;其它(welfare/空/旧端不带)维持通用「信息流广告奖励」。
    # 客户端按此 biz_type 直显固定文案(见 CoinHistoryViewModel.coinTitle),故 remark 只作后台留痕/兜底。
    if feed_scene == "comparison":
        reward_biz, reward_remark = "feed_ad_reward_comparison", "比价奖励"
    elif feed_scene == "coupon":
        reward_biz, reward_remark = "feed_ad_reward_coupon", "领券奖励"
    else:
        reward_biz, reward_remark = "feed_ad_reward", "信息流广告奖励"
    crud_wallet.grant_coins(
        db, user_id, coin,
        biz_type=reward_biz, ref_id=client_event_id,
        remark=reward_remark,
    )
    rec = AdFeedRewardRecord(
        client_event_id=client_event_id,
        user_id=user_id,
        reward_date=today,
        duration_seconds=safe_duration,
        unit_count=unit_count,
        ad_session_id=ad_session_id,
        ecpm_raw=ecpm,
        adn=adn,
        slot_id=slot_id,
        feed_scene=feed_scene,
        trace_id=trace_id,
        app_env=app_env,
        our_code_id=our_code_id,
        coin=coin,
        status="granted",
    )
    return _commit_record(db, rec, client_event_id)


def _commit_record(db: Session, rec: AdFeedRewardRecord, client_event_id: str) -> AdFeedRewardRecord:
    db.add(rec)
    try:
        db.commit()
    except IntegrityError:
        db.rollback()
        existing = _find_by_event(db, client_event_id)
        if existing is not None:
            return existing
        raise
    db.refresh(rec)
    return rec
