"""FEAT-019 业务层限流单测。

覆盖：
- check_rate_limit 首次通过、达到上限抛 RateLimitExceeded
- 窗口过期后重置
- unknown endpoint 报 ValueError
- check_group 批量检查 min + hour
- prune_old 删老日志
"""

from __future__ import annotations

import pytest

from xiangqin import rate_limit


def _fake_now(monkeypatch: pytest.MonkeyPatch, ts: int) -> None:
    """把 rate_limit 模块里引用的 now_ts 固定成 ts。"""
    monkeypatch.setattr(rate_limit, "now_ts", lambda: ts)


def test_check_rate_limit_first_call_passes():
    rate_limit.check_rate_limit("u1", "mail_send_min")


def test_check_rate_limit_hits_limit(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    # mail_send_min 限 10 次
    for _ in range(10):
        rate_limit.check_rate_limit("u1", "mail_send_min")
    with pytest.raises(rate_limit.RateLimitExceeded) as ei:
        rate_limit.check_rate_limit("u1", "mail_send_min")
    assert ei.value.endpoint == "mail_send_min"
    assert ei.value.limit == 10
    assert ei.value.window_s == 60


def test_check_rate_limit_independent_per_user(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    for _ in range(10):
        rate_limit.check_rate_limit("u1", "mail_send_min")
    # u2 不受 u1 影响
    rate_limit.check_rate_limit("u2", "mail_send_min")


def test_check_rate_limit_independent_per_endpoint(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    for _ in range(10):
        rate_limit.check_rate_limit("u1", "mail_send_min")
    # query_min 不受 mail_send_min 影响
    rate_limit.check_rate_limit("u1", "query_min")


def test_check_rate_limit_window_resets(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    for _ in range(10):
        rate_limit.check_rate_limit("u1", "mail_send_min")
    with pytest.raises(rate_limit.RateLimitExceeded):
        rate_limit.check_rate_limit("u1", "mail_send_min")

    # 跳 61 秒，老记录全部超过 60s 窗口
    _fake_now(monkeypatch, 1_000_000 + 61)
    rate_limit.check_rate_limit("u1", "mail_send_min")


def test_check_rate_limit_unknown_endpoint():
    with pytest.raises(ValueError, match="unknown rate-limit endpoint"):
        rate_limit.check_rate_limit("u1", "nonexistent_endpoint")


def test_check_group_mail_send(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    # 一次 check_group 同时消耗 min + hour 各一个配额
    for _ in range(10):
        rate_limit.check_group("u1", "mail_send")
    # 第 11 次：min 窗已满
    with pytest.raises(rate_limit.RateLimitExceeded) as ei:
        rate_limit.check_group("u1", "mail_send")
    assert ei.value.endpoint == "mail_send_min"


def test_check_group_hour_limit(monkeypatch: pytest.MonkeyPatch):
    """10 个 min 窗 * 10 次 = 100 次，打满 mail_send_hour 上限。"""
    base = 1_000_000
    # 每窗隔 65s：min 窗（60s）每次都重置，hour 窗（3600s）累计
    for i in range(10):
        _fake_now(monkeypatch, base + i * 65)
        for _ in range(10):
            rate_limit.check_group("u1", "mail_send")

    # 下一个 min 窗，min 空的但 hour 已满 100
    _fake_now(monkeypatch, base + 10 * 65)
    with pytest.raises(rate_limit.RateLimitExceeded) as ei:
        rate_limit.check_group("u1", "mail_send")
    assert ei.value.endpoint == "mail_send_hour"


def test_check_group_query(monkeypatch: pytest.MonkeyPatch):
    _fake_now(monkeypatch, 1_000_000)
    # query_min = 30
    for _ in range(30):
        rate_limit.check_group("u1", "query")
    with pytest.raises(rate_limit.RateLimitExceeded) as ei:
        rate_limit.check_group("u1", "query")
    assert ei.value.endpoint == "query_min"


def test_check_group_unknown_is_noop():
    """unknown group 不抛 —— 静默忽略，避免后续误用挂掉业务。"""
    rate_limit.check_group("u1", "unknown_group")


def test_prune_old_removes_aged_rows(monkeypatch: pytest.MonkeyPatch):
    # 插入一些老日志（8 天前）+ 新日志（今天）
    base = 2_000_000
    _fake_now(monkeypatch, base - 8 * 86400)
    for _ in range(3):
        rate_limit.check_rate_limit("u1", "mail_send_min")

    _fake_now(monkeypatch, base)
    for _ in range(2):
        rate_limit.check_rate_limit("u1", "mail_send_min")

    # prune 保留 7 天
    deleted = rate_limit.prune_old(older_than_days=7)
    assert deleted == 3

    # 新日志还在
    from xiangqin.db import get_conn

    with get_conn() as conn:
        row = conn.execute("SELECT COUNT(*) AS n FROM rate_limit_log").fetchone()
    assert row["n"] == 2


def test_rate_limit_exceeded_has_informative_message():
    e = rate_limit.RateLimitExceeded("mail_send_min", 10, 60)
    assert "mail_send_min" in str(e)
    assert "10" in str(e)
    assert "1 分钟" in str(e)
