"""首页三统计(帮助用户 / 完成比价 / 累计节省)展示值计算 + 运营配置读写。

三种模式见 app/models/ops_stat_config.py。统一「定时刷新」:展示值 random_current 只在跨过
「北京钟点边界」时刷新一次(real 快照查库 / manual 取固定值 / random 增长)。

random 自增长支持两种方式(random_kind):
  - mult:×随机倍率([min,max]/1000,恒 ≥1.0)——复利、指数增长
  - add :+随机绝对增量([step_min,step_max])——线性、可控,更像真实平台日增

真实值(real)模式展示 = max(真实统计, real_offset 保底值):冷启动显示保底撑场面,真实值涨过
保底后显示纯真实、零差距(real_offset 列名沿用,语义从"偏移"改为"保底")。

「只增不减」护栏(allow_decrease=False,默认):real/manual 刷新时若新目标值低于当前展示值,
保持当前值不回退——门面数字最忌当众缩水。random 天然只增(倍率≥1 / 增量≥0)。

纯门面数字,**接受 tick 时刻的微小并发竞态**(不加行锁):极端情况某次多走一档,无业务后果。
"""
from __future__ import annotations

import random
from datetime import datetime, timezone

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

from app.models.comparison import ComparisonRecord
from app.models.ops_stat_config import OpsStatConfig

# 指标顺序 + 展示元信息(label/unit 给运营后台用;total_saved 单位是分,客户端 ÷100 显示元)
METRICS = ("help_users", "total_compares", "total_saved")
_META = {
    "help_users": ("帮助用户", "人"),
    "total_compares": ("完成比价", "次"),
    "total_saved": ("累计节省", "分"),
}
# 初始播种值(= 客户端原写死门面数:12847 人 / 86532 次 / 37621.4 元)。
_SEED_VALUE = {
    "help_users": 12847,
    "total_compares": 86532,
    "total_saved": 3762140,  # 37621.40 元 = 3762140 分
}

# 防呆边界
_MULT_FLOOR = 1000  # 倍率千分比下限 = 1.000(只增不减)
_MULT_CAP = 1500  # 倍率千分比上限 = 1.500(收紧:单次 ×1.5 已不小,>此一跳就假)
_STEP_CAP = 10**12  # 绝对增量单次上限(基础单位防呆)
_TICK_MIN_SECONDS = 60  # tick 周期下限
_MAX_CATCHUP_PERIODS = 3650  # 单次最多补走的周期数(防 last 远古时一次走爆)
_BEIJING_OFFSET = 8 * 3600  # 北京时间 = UTC + 8h(钟点对齐按北京时间算)
# 真实 total_saved 求和:单条 saved 超此值视为异常 / bug 值,不计入
# (与轮播口径一致,防一条 bug 大额配合「只增不减」永久撑高门面)
_REAL_SAVED_CAP_CENTS = 30000  # 300 元


def _as_utc(dt: datetime | None) -> datetime | None:
    """把(可能朴素的 SQLite)datetime 归一成带时区 UTC;朴素值按 UTC 解释。"""
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc)


# ===== 真实数据(real 模式)=====
def _real_value(db: Session, metric: str) -> int:
    """real 口径:仅统计 status='success' 的比价记录。
    help_users=去重用户数 / total_compares=记录数 / total_saved=省额(分)求和(剔除单条异常大值)。
    """
    success = ComparisonRecord.status == "success"
    if metric == "help_users":
        return db.execute(
            select(func.count(func.distinct(ComparisonRecord.user_id))).where(success)
        ).scalar_one()
    if metric == "total_compares":
        return db.execute(
            select(func.count(ComparisonRecord.id)).where(success)
        ).scalar_one()
    if metric == "total_saved":
        # 只累加 0 < saved ≤ 上限 的条目,异常大值不计入(防 bug 值撑高门面)
        return db.execute(
            select(func.coalesce(func.sum(ComparisonRecord.saved_amount_cents), 0)).where(
                success,
                ComparisonRecord.saved_amount_cents > 0,
                ComparisonRecord.saved_amount_cents <= _REAL_SAVED_CAP_CENTS,
            )
        ).scalar_one()
    raise KeyError(f"unknown metric: {metric}")


# ===== 行管理 =====
def _ensure_rows(db: Session) -> dict[str, OpsStatConfig]:
    """保证三指标行都存在(migration 已播种;这里是缺行兜底,默认 manual + 种子值)。"""
    rows = {r.metric: r for r in db.execute(select(OpsStatConfig)).scalars().all()}
    created = False
    for metric in METRICS:
        if metric not in rows:
            row = OpsStatConfig(
                metric=metric, mode="manual", manual_value=_SEED_VALUE[metric]
            )
            db.add(row)
            rows[metric] = row
            created = True
    if created:
        db.commit()
    return rows


# ===== random 惰性 tick(北京钟点对齐)=====
def _boundary_index(epoch_utc: int, interval: int, anchor_sec: int) -> int:
    """该 UTC 时刻落在第几个「北京钟点边界」。边界 = 北京时间 (anchor + k*interval)。"""
    bj = epoch_utc + _BEIJING_OFFSET
    return (bj - anchor_sec) // interval


def _current_target(db: Session, row: OpsStatConfig) -> int:
    """某模式此刻「应有的展示值」(用于播种 / real·manual 的定时快照)。"""
    if row.mode == "manual":
        return max(0, int(row.manual_value or 0))
    if row.mode == "real":
        # 真实值保底:展示 = max(真实值, 保底值)。冷启动真实值小→显示保底撑场面;
        # 真实值涨过保底后显示纯真实、零差距(real_offset 列名沿用,语义已从"偏移"改为"保底")。
        return max(0, int(_real_value(db, row.metric)), int(row.real_offset or 0))
    # random:无显式初始基数时用真实值(纯真实,不含偏移)播种
    return max(0, int(_real_value(db, row.metric)))


def _monotonic(row: OpsStatConfig, new_val: int) -> int:
    """只增不减护栏:未开 allow_decrease 且已有展示值时,新值不得低于当前值(否则保持当前)。"""
    if not row.allow_decrease and row.random_current is not None and new_val < row.random_current:
        return row.random_current
    return new_val


def _grow(row: OpsStatConfig, val: int, periods: int) -> int:
    """random 自增长:按 random_kind 走 periods 个周期(add=逐周期加随机增量 / mult=逐周期乘随机倍率)。"""
    if row.random_kind == "add":
        lo = max(0, row.random_step_min)
        hi = max(lo, row.random_step_max)
        for _ in range(periods):
            val += random.randint(lo, hi)
    else:
        lo = max(_MULT_FLOOR, row.random_mult_min)
        hi = max(lo, row.random_mult_max)
        for _ in range(periods):
            val = val * random.randint(lo, hi) // 1000
    return val


def _refresh(db: Session, row: OpsStatConfig) -> int:
    """统一「定时刷新」:展示值 random_current 只在跨过「北京钟点边界」时刷新一次。
    real→重新快照(真实+偏移,受只增不减护栏)/ manual→取固定值(受护栏)/ random→按方式增长。
    无初值则按模式播种。返回当前展示值(基础单位)。
    """
    now = datetime.now(timezone.utc)
    if row.random_current is None or row.random_last_tick_at is None:
        row.random_current = _current_target(db, row)
        row.random_last_tick_at = now
        db.commit()
        return row.random_current

    interval = max(_TICK_MIN_SECONDS, row.random_tick_seconds)
    anchor_sec = (max(0, row.random_anchor_minutes or 0) * 60) % interval
    last = _as_utc(row.random_last_tick_at)
    periods = _boundary_index(int(now.timestamp()), interval, anchor_sec) - _boundary_index(
        int(last.timestamp()), interval, anchor_sec
    )
    if periods <= 0:
        return row.random_current

    if row.mode == "random":
        row.random_current = _grow(row, row.random_current, min(periods, _MAX_CATCHUP_PERIODS))
    else:
        # real / manual:跨多少边界都只取「此刻应有的值」快照一次,经只增不减护栏
        row.random_current = _monotonic(row, _current_target(db, row))
    row.random_last_tick_at = now  # 用边界索引比较,直接记 now 不会重复计
    db.commit()
    return row.random_current


# ===== 用户侧:展示值 =====
def get_display_values(db: Session) -> dict[str, int]:
    """返回 {metric: 展示整数值}(total_saved 为分)。三模式统一走定时刷新。"""
    rows = _ensure_rows(db)
    return {metric: int(_refresh(db, rows[metric])) for metric in METRICS}


# ===== 运营侧:配置读写 =====
def _snapshot(row: OpsStatConfig) -> dict:
    """审计 / 返回用的行快照。"""
    return {
        "mode": row.mode,
        "manual_value": row.manual_value,
        "random_mult_min": row.random_mult_min,
        "random_mult_max": row.random_mult_max,
        "random_tick_seconds": row.random_tick_seconds,
        "random_anchor_minutes": row.random_anchor_minutes,
        "random_kind": row.random_kind,
        "random_step_min": row.random_step_min,
        "random_step_max": row.random_step_max,
        "real_offset": row.real_offset,
        "allow_decrease": row.allow_decrease,
        "random_current": row.random_current,
        "random_last_tick_at": (
            row.random_last_tick_at.isoformat() if row.random_last_tick_at else None
        ),
    }


def get_config(db: Session) -> list[dict]:
    """三指标当前配置 + 真实值预览(给运营对比),按 METRICS 顺序。

    先跑一次定时刷新,使「当前展示值」在后台轮询时也随钟点实时推进。
    """
    rows = _ensure_rows(db)
    out: list[dict] = []
    for metric in METRICS:
        row = rows[metric]
        _refresh(db, row)
        label, unit = _META[metric]
        out.append(
            {
                "metric": metric,
                "label": label,
                "unit": unit,
                "real_value": _real_value(db, metric),  # 纯真实(不含偏移),供运营对比
                "updated_at": row.updated_at.isoformat() if row.updated_at else None,
                **_snapshot(row),
            }
        )
    return out


def update_config(
    db: Session,
    metric: str,
    *,
    mode: str | None = None,
    manual_value: int | None = None,
    random_mult_min: int | None = None,
    random_mult_max: int | None = None,
    random_tick_seconds: int | None = None,
    random_anchor_minutes: int | None = None,
    random_kind: str | None = None,
    random_step_min: int | None = None,
    random_step_max: int | None = None,
    real_offset: int | None = None,
    allow_decrease: bool | None = None,
    random_initial: int | None = None,
    apply_now: bool = False,
    admin_id: int,
    commit: bool = True,
) -> tuple[dict, dict]:
    """改某指标配置。返回 (before, after) 快照供审计。非法入参抛 ValueError(router 转 400)。

    展示值 random_current 采用统一「定时刷新」:平时不动,到更新钟点才按模式刷新。
    本函数只在「首次无值」时按模式播种;给了 random_initial 则立即设为该值并重置;
    apply_now=True 则立即刷新一次(random 走一档增长,real/manual 取应有值,均经只增不减护栏)。
    """
    if metric not in METRICS:
        raise ValueError(f"未知指标: {metric}")
    rows = _ensure_rows(db)
    row = rows[metric]
    before = _snapshot(row)

    if mode is not None:
        if mode not in ("real", "manual", "random"):
            raise ValueError("mode 需为 real/manual/random")
        row.mode = mode
    if manual_value is not None:
        if manual_value < 0:
            raise ValueError("manual_value 需为非负整数")
        row.manual_value = manual_value
    if random_mult_min is not None:
        if not (_MULT_FLOOR <= random_mult_min <= _MULT_CAP):
            raise ValueError(f"倍率下限(千分比)需在 {_MULT_FLOOR}~{_MULT_CAP}")
        row.random_mult_min = random_mult_min
    if random_mult_max is not None:
        if not (_MULT_FLOOR <= random_mult_max <= _MULT_CAP):
            raise ValueError(f"倍率上限(千分比)需在 {_MULT_FLOOR}~{_MULT_CAP}")
        row.random_mult_max = random_mult_max
    if row.random_mult_max < row.random_mult_min:
        raise ValueError("倍率上限不得小于下限")
    if random_tick_seconds is not None:
        if random_tick_seconds < _TICK_MIN_SECONDS:
            raise ValueError(f"更新间隔不得小于 {_TICK_MIN_SECONDS} 秒")
        row.random_tick_seconds = random_tick_seconds
    if random_anchor_minutes is not None:
        if not (0 <= random_anchor_minutes < 1440):
            raise ValueError("更新时间需在 00:00~23:59 之间")
        row.random_anchor_minutes = random_anchor_minutes
    if random_kind is not None:
        if random_kind not in ("mult", "add"):
            raise ValueError("增长方式需为 mult/add")
        row.random_kind = random_kind
    if random_step_min is not None:
        if not (0 <= random_step_min <= _STEP_CAP):
            raise ValueError("增量下限需为非负整数")
        row.random_step_min = random_step_min
    if random_step_max is not None:
        if not (0 <= random_step_max <= _STEP_CAP):
            raise ValueError("增量上限需为非负整数")
        row.random_step_max = random_step_max
    if row.random_step_max < row.random_step_min:
        raise ValueError("增量上限不得小于下限")
    if real_offset is not None:
        if real_offset < 0:
            raise ValueError("保底值需为非负整数")
        row.real_offset = real_offset
    if allow_decrease is not None:
        row.allow_decrease = allow_decrease

    now = datetime.now(timezone.utc)
    if random_initial is not None:
        if random_initial < 0:
            raise ValueError("初始基数需为非负整数")
        # 初始基数受「只增不减」护栏(与 manual/real 一致):未勾 allow_decrease 且低于
        # 当前展示值时保持现值。首次无值(random_current is None)则护栏放行、直接设。
        row.random_current = _monotonic(row, random_initial)
        row.random_last_tick_at = now
    elif apply_now:
        # 立即更新:不等钟点,马上刷新一次
        if row.mode == "random" and row.random_current is not None:
            row.random_current = _grow(row, row.random_current, 1)
        else:
            row.random_current = _monotonic(row, _current_target(db, row))
        row.random_last_tick_at = now
    elif row.random_current is None:
        # 首次无值:按当前模式播种,使配置后立即有合理展示值
        row.random_current = _current_target(db, row)
        row.random_last_tick_at = now

    row.updated_by_admin_id = admin_id
    if commit:
        db.commit()
        db.refresh(row)
    else:
        db.flush()
    return before, _snapshot(row)
