"""admin 大盘聚合查询(全局 count/sum/DAU/成功率)。全部只读、不改任何数据。

⚠️ 性能:这些是全表 count/sum,P0 数据量小够用;用户量上来后热点字段(user.created_at /
user.last_login_at / comparison_record.status / withdraw_order.status)要加索引,或改增量统计表。
"""
from __future__ import annotations

from datetime import date, datetime, time, timedelta, timezone
from decimal import Decimal, InvalidOperation

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

from app.models.ad_feed_reward import AdFeedRewardRecord
from app.models.ad_reward import AdRewardRecord
from app.models.comparison import ComparisonRecord
from app.models.coupon_state import CouponPromptEngagement
from app.models.cps_order import CpsOrder
from app.models.feedback import Feedback
from app.models.savings import SavingsRecord
from app.models.signin import SigninBoostRecord, SigninRecord
from app.models.user import User
from app.models.wallet import CoinTransaction, WithdrawOrder

_BEIJING = timezone(timedelta(hours=8))
COUPON_REWARD_BIZ_TYPES = ("reward_video", "ad_reward", "coupon", "coupon_reward")
COMPARISON_REWARD_BIZ_TYPES = ("comparison", "compare_reward", "comparison_reward")
EXCLUDED_REWARD_BIZ_TYPES = ("invite_inviter", "invite_invitee", "admin_grant")
UNCLASSIFIED_FEED_BIZ_TYPES = ("feed_ad_reward",)
REGULAR_TASK_EXCLUDED_BIZ_TYPES = (
    *COUPON_REWARD_BIZ_TYPES,
    *COMPARISON_REWARD_BIZ_TYPES,
    *EXCLUDED_REWARD_BIZ_TYPES,
    *UNCLASSIFIED_FEED_BIZ_TYPES,
)
MEITUAN_CPS_INVALID_STATUSES = ("4", "5")
MEITUAN_CPS_SETTLED_STATUS = "6"


def _beijing_today_start_utc() -> datetime:
    """北京时间今天 0 点对应的 UTC 时刻(DAU / 今日新增按北京时区切天)。"""
    now_bj = datetime.now(_BEIJING)
    start_bj = now_bj.replace(hour=0, minute=0, second=0, microsecond=0)
    return start_bj.astimezone(timezone.utc)


def today_dau(db: Session) -> int:
    """今日活跃用户数(DAU):北京时区今天 0 点后登录过(last_login_at)。

    广告收益报表复用这个函数;历史窗口 DAU 由 dashboard_overview 的 period 口径另算。
    """
    today_start = _beijing_today_start_utc()
    return int(
        db.execute(select(func.count(User.id)).where(User.last_login_at >= today_start)).scalar_one()
    )


def _default_period_end() -> date:
    """新版大盘不含今日,默认窗口结束日=北京时间昨天。"""
    return datetime.now(_BEIJING).date() - timedelta(days=1)


def _normalize_period(date_from: date | None, date_to: date | None) -> tuple[date, date]:
    end = date_to or _default_period_end()
    start = date_from or end
    if start > end:
        start, end = end, start
    return start, end


def _period_bounds(date_from: date, date_to: date) -> tuple[datetime, datetime, datetime, datetime]:
    """返回同一北京自然日窗口的 UTC aware 边界和北京 naive 边界。

    user.created_at / last_login_at 是 UTC aware 口径;比较/金币等历史上有北京 naive
    写入,所以两套边界同时保留。
    """
    start_bj = datetime.combine(date_from, time.min, tzinfo=_BEIJING)
    end_bj = datetime.combine(date_to + timedelta(days=1), time.min, tzinfo=_BEIJING)
    start_utc = start_bj.astimezone(timezone.utc)
    end_utc = end_bj.astimezone(timezone.utc)
    return (
        start_utc,
        end_utc,
        start_bj.replace(tzinfo=None),
        end_bj.replace(tzinfo=None),
    )


def _date_range(date_from: date, date_to: date) -> list[date]:
    days = (date_to - date_from).days
    return [date_from + timedelta(days=i) for i in range(days + 1)]


def _commission_rate_percent(raw: str | None) -> Decimal | None:
    """美团 commissionRate 原值: "300"=3%, "10"=0.1%;也兼容 "3%"。"""
    if raw is None:
        return None
    s = str(raw).strip()
    if not s:
        return None
    try:
        if s.endswith("%"):
            return Decimal(s[:-1])
        val = Decimal(s)
    except (InvalidOperation, ValueError):
        return None
    return val / Decimal("100")


def dashboard_overview(
    db: Session, *, date_from: date | None = None, date_to: date | None = None
) -> dict:
    today_start = _beijing_today_start_utc()
    period_from, period_to = _normalize_period(date_from, date_to)
    start_utc, end_utc, start_local, end_local = _period_bounds(period_from, period_to)

    def _count(model, *conds) -> int:
        stmt = select(func.count(model.id))
        if conds:
            stmt = stmt.where(*conds)
        return int(db.execute(stmt).scalar_one())

    def _sum(col, *conds) -> int:
        stmt = select(func.coalesce(func.sum(col), 0))
        if conds:
            stmt = stmt.where(*conds)
        return int(db.execute(stmt).scalar_one())

    def _user_id_set(stmt) -> set[int]:
        return {int(v) for v in db.execute(stmt).scalars().all() if v is not None}

    # ===== 用户 =====
    by_status = dict(
        db.execute(select(User.status, func.count(User.id)).group_by(User.status)).all()
    )

    # ===== 提现状态分布 =====
    wd_by_status = dict(
        db.execute(
            select(WithdrawOrder.status, func.count(WithdrawOrder.id)).group_by(
                WithdrawOrder.status
            )
        ).all()
    )

    # ===== 比价 =====
    comparison_total = _count(ComparisonRecord)
    comparison_success = _count(ComparisonRecord, ComparisonRecord.status == "success")
    success_rate = round(comparison_success / comparison_total, 4) if comparison_total else 0.0
    period_comparison_conds = (
        ComparisonRecord.created_at >= start_local,
        ComparisonRecord.created_at < end_local,
    )
    period_comparison_total = _count(ComparisonRecord, *period_comparison_conds)
    period_comparison_success = _count(
        ComparisonRecord,
        *period_comparison_conds,
        ComparisonRecord.status == "success",
    )
    period_comparison_success_rate = (
        round(period_comparison_success / period_comparison_total, 4)
        if period_comparison_total
        else 0.0
    )
    period_saved_positive_count = _count(
        ComparisonRecord,
        *period_comparison_conds,
        ComparisonRecord.status == "success",
        ComparisonRecord.saved_amount_cents > 0,
    )
    period_saved_positive_sum = _sum(
        ComparisonRecord.saved_amount_cents,
        *period_comparison_conds,
        ComparisonRecord.status == "success",
        ComparisonRecord.saved_amount_cents > 0,
    )
    period_avg_saved_cents = (
        round(period_saved_positive_sum / period_saved_positive_count)
        if period_saved_positive_count
        else None
    )
    period_avg_duration_ms = db.execute(
        select(func.avg(ComparisonRecord.total_ms)).where(
            *period_comparison_conds,
            ComparisonRecord.total_ms.is_not(None),
            ComparisonRecord.total_ms > 0,
        )
    ).scalar_one()
    period_avg_duration_ms = (
        round(float(period_avg_duration_ms))
        if period_avg_duration_ms is not None
        else None
    )

    ordered_exists = (
        select(SavingsRecord.id)
        .where(
            SavingsRecord.user_id == ComparisonRecord.user_id,
            SavingsRecord.source == "compare",
            SavingsRecord.shop_name.is_not(None),
            SavingsRecord.shop_name == ComparisonRecord.store_name,
        )
        .exists()
    )
    period_ordered_count = _count(
        ComparisonRecord,
        *period_comparison_conds,
        ComparisonRecord.store_name.is_not(None),
        ordered_exists,
    )

    # ===== 日期窗口用户 =====
    period_new_user_ids = _user_id_set(
        select(User.id).where(User.created_at >= start_utc, User.created_at < end_utc)
    )
    login_user_ids = _user_id_set(
        select(User.id).where(User.last_login_at >= start_utc, User.last_login_at < end_utc)
    )
    compare_user_ids = _user_id_set(
        select(ComparisonRecord.user_id).where(*period_comparison_conds)
    )
    coupon_user_ids = _user_id_set(
        select(CouponPromptEngagement.user_id).where(
            CouponPromptEngagement.engage_date >= period_from,
            CouponPromptEngagement.engage_date <= period_to,
            CouponPromptEngagement.engage_type == "claim_started",
        )
    )
    period_active_user_ids = login_user_ids | compare_user_ids | coupon_user_ids
    period_retained_new_user_ids = period_new_user_ids & period_active_user_ids
    period_retention_rate = (
        round(len(period_retained_new_user_ids) / len(period_new_user_ids), 4)
        if period_new_user_ids
        else None
    )
    trend_points: list[dict] = []
    for cur_date in _date_range(period_from, period_to):
        day_start_utc, day_end_utc, day_start_local, day_end_local = _period_bounds(
            cur_date, cur_date
        )
        daily_comparison_conds = (
            ComparisonRecord.created_at >= day_start_local,
            ComparisonRecord.created_at < day_end_local,
        )
        daily_login_user_ids = _user_id_set(
            select(User.id).where(
                User.last_login_at >= day_start_utc,
                User.last_login_at < day_end_utc,
            )
        )
        daily_compare_user_ids = _user_id_set(
            select(ComparisonRecord.user_id).where(*daily_comparison_conds)
        )
        daily_coupon_user_ids = _user_id_set(
            select(CouponPromptEngagement.user_id).where(
                CouponPromptEngagement.engage_date == cur_date,
                CouponPromptEngagement.engage_type == "claim_started",
            )
        )
        trend_points.append(
            {
                "date": cur_date,
                "active_users": len(
                    daily_login_user_ids | daily_compare_user_ids | daily_coupon_user_ids
                ),
                "new_users": _count(
                    User,
                    User.created_at >= day_start_utc,
                    User.created_at < day_end_utc,
                ),
                "comparisons": _count(ComparisonRecord, *daily_comparison_conds),
            }
        )

    period_coin_conds = (
        CoinTransaction.created_at >= start_local,
        CoinTransaction.created_at < end_local,
        CoinTransaction.amount > 0,
    )
    period_reward_video_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type.in_(("reward_video", "ad_reward")),
    )
    period_feed_ad_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type == "feed_ad_reward",
    )
    period_signin_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type == "signin",
    )
    period_signin_boost_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type == "signin_boost",
    )
    period_task_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type.like("task_%"),
    )
    period_coupon_reward_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type.in_(COUPON_REWARD_BIZ_TYPES),
    )
    period_comparison_reward_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type.in_(COMPARISON_REWARD_BIZ_TYPES),
    )
    period_regular_task_coin_total = _sum(
        CoinTransaction.amount,
        *period_coin_conds,
        CoinTransaction.biz_type.notin_(REGULAR_TASK_EXCLUDED_BIZ_TYPES),
    )
    period_meituan_orders = list(
        db.execute(
            select(CpsOrder).where(
                CpsOrder.pay_time >= start_utc,
                CpsOrder.pay_time < end_utc,
            )
        ).scalars()
    )
    period_meituan_valid_orders = [
        o for o in period_meituan_orders if o.mt_status not in MEITUAN_CPS_INVALID_STATUSES
    ]
    period_meituan_hit_count = 0
    period_meituan_miss_count = 0
    period_meituan_unknown_rate_count = 0
    for order in period_meituan_valid_orders:
        rate = _commission_rate_percent(order.commission_rate)
        if rate is None:
            period_meituan_unknown_rate_count += 1
        elif rate < Decimal("1"):
            period_meituan_miss_count += 1
        else:
            period_meituan_hit_count += 1
    period_meituan_hit_denominator = period_meituan_hit_count + period_meituan_miss_count
    period_meituan_hit_rate = (
        round(period_meituan_hit_count / period_meituan_hit_denominator, 4)
        if period_meituan_hit_denominator
        else None
    )

    return {
        "users": {
            "total": _count(User),
            "active": by_status.get("active", 0),
            "disabled": by_status.get("disabled", 0),
            "deleted": by_status.get("deleted", 0),
            "new_today": _count(User, User.created_at >= today_start),
            "dau": today_dau(db),
        },
        "coins": {
            # 累计发放金币(coin_transaction 里所有 amount>0 之和;负数是兑换/扣减不计)
            "granted_total": _sum(CoinTransaction.amount, CoinTransaction.amount > 0),
            "reward_video_coin_total": _sum(
                CoinTransaction.amount,
                CoinTransaction.amount > 0,
                CoinTransaction.biz_type.in_(("reward_video", "ad_reward")),
            ),
            "reward_video_watch_count": _count(
                AdRewardRecord,
                AdRewardRecord.reward_scene == "reward_video",
                AdRewardRecord.status == "granted",
            ),
            "feed_ad_coin_total": _sum(
                CoinTransaction.amount,
                CoinTransaction.amount > 0,
                CoinTransaction.biz_type == "feed_ad_reward",
            ),
            "feed_ad_watch_count": _count(
                AdFeedRewardRecord,
                AdFeedRewardRecord.status == "granted",
            ),
            "signin_coin_total": _sum(
                CoinTransaction.amount,
                CoinTransaction.amount > 0,
                CoinTransaction.biz_type == "signin",
            ),
            "signin_count": _count(SigninRecord),
            "signin_boost_coin_total": _sum(
                CoinTransaction.amount,
                CoinTransaction.amount > 0,
                CoinTransaction.biz_type == "signin_boost",
            ),
            "signin_boost_watch_count": _count(SigninBoostRecord),
        },
        "cash": {
            "withdraw_success_cents": _sum(
                WithdrawOrder.amount_cents, WithdrawOrder.status == "success"
            ),
            "withdraw_pending_count": wd_by_status.get("pending", 0),
            "withdraw_success_count": wd_by_status.get("success", 0),
            "withdraw_failed_count": wd_by_status.get("failed", 0),
        },
        "comparison": {
            "total": comparison_total,
            "success": comparison_success,
            "success_rate": success_rate,
        },
        "period": {
            "date_from": period_from,
            "date_to": period_to,
            "users": {
                "new": len(period_new_user_ids),
                "active": len(period_active_user_ids),
                "retained_new_users": len(period_retained_new_user_ids),
                "retention_rate": period_retention_rate,
                "retention_note": (
                    "近似口径:登录(last_login_at)+已上报比价记录+领券claim_started;"
                    "尚不包含未完成上报的比价开始事件"
                ),
            },
            "comparison": {
                "total": period_comparison_total,
                "success": period_comparison_success,
                "success_rate": period_comparison_success_rate,
                "ordered": period_ordered_count,
                "average_duration_ms": period_avg_duration_ms,
                "average_saved_cents": period_avg_saved_cents,
            },
            "coins": {
                "granted_total": _sum(CoinTransaction.amount, *period_coin_conds),
                "reward_video_coin_total": period_reward_video_coin_total,
                "feed_ad_coin_total": period_feed_ad_coin_total,
                "signin_coin_total": period_signin_coin_total,
                "signin_boost_coin_total": period_signin_boost_coin_total,
                "task_coin_total": period_task_coin_total,
                "coupon_reward_coin_total": period_coupon_reward_coin_total,
                "comparison_reward_coin_total": period_comparison_reward_coin_total,
                "regular_task_coin_total": period_regular_task_coin_total,
            },
            "cash": {
                "withdraw_success_cents": _sum(
                    WithdrawOrder.amount_cents,
                    WithdrawOrder.status == "success",
                    WithdrawOrder.created_at >= start_local,
                    WithdrawOrder.created_at < end_local,
                ),
            },
            "trend": trend_points,
        },
        "feedback": {
            "new": _count(Feedback, Feedback.status.in_(("pending", "new"))),
        },
        "cps": {
            "available": True,
            "note": "美团 CPS 读 cps_order 对账订单;淘宝/京东佣金暂空",
            "meituan_order_count": len(period_meituan_valid_orders),
            "meituan_commission_cents": sum(
                o.commission_cents or 0 for o in period_meituan_valid_orders
            ),
            "meituan_hit_count": period_meituan_hit_count,
            "meituan_miss_count": period_meituan_miss_count,
            "meituan_unknown_rate_count": period_meituan_unknown_rate_count,
            "meituan_hit_rate": period_meituan_hit_rate,
        },
    }
