#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""WP-PMAX-01 / WP-WX-02 离线回归：防封限流内存态逻辑

真机铁律口径：本测试**仅离线回归**内存态频控逻辑（不依赖 Redis/真机），
验证 AB-02 静默限流熔断 / AB-03 长暂停 + 高危动作互斥 / AB-06 风险分级的纯函数行为；
**不替代**真机批量发送/加友的频控 E2E。

运行：python3 sdk/tests/test_rate_limiter_antiban.py
"""
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "sdk" / "app"))

from services import rate_limiter as rl  # noqa: E402

fails = 0


def check(cond, msg):
    global fails
    if cond:
        print(f"  ✓ {msg}")
    else:
        print(f"  ✗ FAIL: {msg}")
        fails += 1


print("[WP-PMAX-01] 防封限流离线回归")
lim = rl.AntiDetectRateLimiter()
DEV = "test_dev_offline"

# ---- AB-02 静默限流熔断 ----
print("· AB-02 静默限流熔断")
lim.reset_silent_throttle(DEV)
# 未触发时不应熔断
try:
    lim._check_silent_throttle(DEV)
    check(True, "初始无熔断，写类放行")
except rl.SilentThrottleCooldown:
    check(False, "初始不应熔断")

cd1 = lim.trip_silent_throttle(DEV)
check(rl.SILENT_THROTTLE_COOLDOWN[0] <= cd1 <= rl.SILENT_THROTTLE_COOLDOWN[1] * 1.01,
      f"首次熔断冷却在 30-60min 区间（{cd1:.0f}s）")
raised = False
try:
    lim._check_silent_throttle(DEV)
except rl.SilentThrottleCooldown as e:
    raised = True
    check(e.hits == 1, "熔断期写类被拒，hits=1")
check(raised, "熔断期 _check_silent_throttle 抛 SilentThrottleCooldown")

cd2 = lim.trip_silent_throttle(DEV)
check(cd2 > cd1, f"二次触发退避升级（{cd2:.0f}s > {cd1:.0f}s）")

# 真 msg_id 回来 → reset 清零
lim.reset_silent_throttle(DEV)
try:
    lim._check_silent_throttle(DEV)
    check(True, "reset 后熔断解除，写类恢复")
except rl.SilentThrottleCooldown:
    check(False, "reset 后不应再熔断")

# ---- AB-03 长暂停节拍 ----
print("· AB-03 长暂停 + 高危互斥")
rl._action_seq.pop(DEV, None)
rl._long_pause_until.pop(DEV, None)
rl._next_pause_at[DEV] = 3  # 强制第 3 个动作触发长暂停
pause_raised = False
for i in range(3):
    try:
        lim._check_long_pause(DEV)
    except rl.LongPauseRequired as e:
        pause_raised = True
        check(rl.LONG_PAUSE_RANGE[0] <= e.wait_seconds <= rl.LONG_PAUSE_RANGE[1],
              f"第{i+1}动作触发长暂停 {e.wait_seconds:.0f}s（10-30min）")
check(pause_raised, "达节拍阈值触发 LongPauseRequired")
# 暂停期内再检查应继续被拦
try:
    lim._check_long_pause(DEV)
    check(False, "长暂停期内应继续拦截")
except rl.LongPauseRequired:
    check(True, "长暂停期内持续 fast-fail")

# 高危动作互斥
rl._last_highrisk.pop(DEV, None)
lim._check_highrisk_combo(DEV, "add_friend")   # 记录首个高危
combo_raised = False
try:
    lim._check_highrisk_combo(DEV, "mass_send")  # 不同类高危，窗口内
except rl.HighRiskComboBlocked as e:
    combo_raised = True
    check(e.wait_seconds > 0, f"高危组合互斥拦截 {e.wait_seconds:.0f}s")
check(combo_raised, "短窗口内混跑不同类高危被拦")
# 同类高危不拦
try:
    lim._check_highrisk_combo(DEV, "add_friend")
    check(True, "同类高危动作不互斥")
except rl.HighRiskComboBlocked:
    check(False, "同类高危不应互斥")

# ---- AB-06 风险分级 ----
print("· AB-06 风险分级")
check(rl.classify_risk("静默限流熔断中") == rl.RISK_NEEDS_HUMAN, "熔断→needs_human")
check(rl.classify_risk("高危动作组合互斥：add_friend") == rl.RISK_BLOCK, "高危互斥→block")
check(rl.classify_risk("当前不在操作时段") == rl.RISK_LOG, "时段→log")
check(rl.classify_risk("长会话中断，需再等 600s") == rl.RISK_LOG, "长暂停→log")

# ---- runtime 看板字段 ----
print("· AB-05 runtime 看板")
st = lim.get_runtime_state(DEV)
check("silent_throttle" in st and "long_pause" in st and "last_highrisk" in st,
      "get_runtime_state 含三段运行态")

# 清理
lim.reset_silent_throttle(DEV)
rl._action_seq.pop(DEV, None)
rl._long_pause_until.pop(DEV, None)
rl._last_highrisk.pop(DEV, None)

print(f"\n[done] 失败 {fails} 条")
sys.exit(1 if fails else 0)
