"""每日拉取穿山甲 GroMore 天级收益报表入库(供 admin 广告收益报表的「穿山甲后台收益」)。

GroMore 数据 API 为 T+1:次日穿山甲约 10:00 出数。建议线上每天 ~10:30 由 systemd timer 跑一次
(默认拉【昨天】);穿山甲对历史数据可能订正,故支持回补近 N 天(幂等 upsert,重跑无害)。

用法:
    python -m scripts.sync_pangle_revenue                 # 拉昨天(北京时间)
    python -m scripts.sync_pangle_revenue --days 3        # 回补昨天起往前 3 天
    python -m scripts.sync_pangle_revenue --date 2026-06-27           # 指定单天
    python -m scripts.sync_pangle_revenue --start 2026-06-01 --end 2026-06-27  # 指定区间

约束(来自官方文档):天级跨度 ≤ 1 个月、不早于 12 个月;「今天」与「今天以前」必须分开查,
本脚本默认只拉昨天及更早,不混查今天。未配置 PANGLE_REPORT_* 时直接 no-op 退出。
"""
from __future__ import annotations

import argparse
import sys
from datetime import date, timedelta

# Windows 控制台按 UTF-8 输出中文/¥
try:
    sys.stdout.reconfigure(encoding="utf-8")  # type: ignore[attr-defined]
except Exception:  # noqa: BLE001
    pass

from app.core.config import settings
from app.core.rewards import cn_today
from app.db.session import SessionLocal
from app.integrations import pangle_report
from app.integrations.pangle_report import PangleReportError
from app.repositories import ad_pangle_revenue as repo

# 拉取维度固定:date×site_id×ad_unit_id。⚠️ 用 ad_unit_id(GroMore 广告位ID = 我们客户端配的 104xxx
# = ad_ecpm_record.our_code_id)而**非** code_id —— 实测 code_id 返回的是底层各 ADN 的代码位
# (如 983674557 / youlianghui_xinxiliu),对不上我们的口径。指标取预估 + Api 两个收益。
_DIMENSIONS = "date,site_id,ad_unit_id"
_METRICS = "revenue,api_revenue,ecpm,imp_cnt"


def _to_float(v: object) -> float | None:
    """接口数值字段都是字符串;空/非法 → None(api_revenue 缺失时即为 None,前端显示「-」)。"""
    if v is None or str(v).strip() == "":
        return None
    try:
        return float(str(v).strip())
    except (TypeError, ValueError):
        return None


def _row_date(row: dict) -> str | None:
    """报表行的日期:优先 date 维度,回退 start_date;取前 10 位 'YYYY-MM-DD'。"""
    raw = row.get("date") or row.get("start_date") or ""
    raw = str(raw).strip()
    return raw[:10] if len(raw) >= 10 else None


def _normalize(rows: list[dict]) -> tuple[list[dict], int, set[str]]:
    """接口原始行 → 入库行;返回 (入库行, 跳过数, 未映射 site_id 集合)。

    site_id 经 settings 映射成 app_env(prod/test);未映射的仍入库(app_env=site:<id>)但提示,
    避免静默丢收益。缺 code_id / 日期的行跳过。
    """
    site_map = settings.pangle_report_site_id_to_env
    out: list[dict] = []
    skipped = 0
    unmapped: set[str] = set()
    for row in rows:
        rdate = _row_date(row)
        # 用 ad_unit_id(GroMore 广告位ID = 我们的 104xxx),不是 code_id(底层 ADN 代码位)
        code_id = str(row.get("ad_unit_id") or "").strip()
        if not rdate or not code_id:
            skipped += 1
            continue
        site_id = str(row.get("site_id") or "").strip()
        app_env = site_map.get(site_id)
        if app_env is None:
            app_env = f"site:{site_id}" if site_id else "unknown"
            if site_id:
                unmapped.add(site_id)
        out.append({
            "report_date": rdate,
            "app_env": app_env,
            "site_id": site_id or None,
            "our_code_id": code_id,
            "adn": "",
            "revenue_yuan": _to_float(row.get("revenue")) or 0.0,
            "api_revenue_yuan": _to_float(row.get("api_revenue")),
            "ecpm": (str(row.get("ecpm")).strip() or None) if row.get("ecpm") is not None else None,
            "impressions": int(_to_float(row.get("imp_cnt")) or 0),
            "currency": "cny",
        })
    return out, skipped, unmapped


def sync_range(start_date: str, end_date: str) -> None:
    if not settings.pangle_report_configured:
        print("PANGLE_REPORT_* 未配置,跳过(no-op)。需在后台「接入中心 → GroMore-API」领取 "
              "user_id/role_id/Security Key 并填入 .env。")
        return

    print(f"拉取 GroMore 天级收益 {start_date} ~ {end_date} (dims={_DIMENSIONS}) ...")
    try:
        raw = pangle_report.fetch_daily_report(
            start_date=start_date, end_date=end_date,
            dimensions=_DIMENSIONS, metrics=_METRICS, time_zone=8,
        )
    except PangleReportError as e:
        print(f"❌ 拉取失败:{e}")
        sys.exit(1)

    rows, skipped, unmapped = _normalize(raw)
    if unmapped:
        print(f"⚠️ 有未映射 site_id(收益仍入库,app_env=site:<id>):{sorted(unmapped)};"
              f"如需归入 prod/test,补 PANGLE_REPORT_SITE_ID_* 配置。")
    with SessionLocal() as db:
        stats = repo.upsert_daily_rows(db, rows)
    total_rev = round(sum(r["revenue_yuan"] for r in rows), 4)
    print(f"✅ 完成:接口 {len(raw)} 行 → 入库 {len(rows)} 行(跳过 {skipped}),"
          f"新增 {stats['inserted']} / 更新 {stats['updated']};预估收益合计 ¥{total_rev}")


def main() -> None:
    ap = argparse.ArgumentParser(description="拉取穿山甲 GroMore 天级收益报表入库")
    ap.add_argument("--date", help="指定单天 YYYY-MM-DD")
    ap.add_argument("--start", help="区间起始 YYYY-MM-DD(与 --end 同用)")
    ap.add_argument("--end", help="区间结束 YYYY-MM-DD(与 --start 同用)")
    ap.add_argument("--days", type=int, help="回补:昨天起往前 N 天(含昨天)")
    args = ap.parse_args()

    yesterday = cn_today() - timedelta(days=1)
    if args.date:
        start = end = args.date
    elif args.start and args.end:
        start, end = args.start, args.end
    elif args.days:
        start = (yesterday - timedelta(days=args.days - 1)).isoformat()
        end = yesterday.isoformat()
    else:
        start = end = yesterday.isoformat()

    # 防呆:跨度 > 31 天直接拒(接口上限 1 个月,超了会返回 114)
    if (date.fromisoformat(end) - date.fromisoformat(start)).days > 31:
        print("❌ 区间跨度超过 31 天(接口上限 1 个月),请分批拉。")
        sys.exit(1)

    sync_range(start, end)


if __name__ == "__main__":
    main()
