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

按 (user_id, endpoint) 滑动窗口计数，存 SQLite 表 rate_limit_log。
check_rate_limit(...) 在 handler 入口调，超限 raise RateLimitExceeded。

限制位置（当前）：
- POST /mail/send  →  10/min, 100/hour
- POST /query      →  30/min, 500/hour

没覆盖：
- register（手机号层已 60s 节流）
- verify-id（realname 层 24h 内失败 3 次锁）

CAPTCHA 未做 —— CLI-native 产品无 web 前端，传统 web CAPTCHA 不适配；
SMS 验证码 + 实名二要素核验已等效"证明真人"。详 issue #019 discussion。
"""

from __future__ import annotations

from .db import get_conn, now_ts


class RateLimitExceeded(Exception):
    """用户触发了限流。HTTP 层应映射到 429 Too Many Requests。"""

    def __init__(self, endpoint: str, limit: int, window_s: int):
        self.endpoint = endpoint
        self.limit = limit
        self.window_s = window_s
        super().__init__(f"rate limit exceeded: {endpoint} 超过 {limit} 次 / {window_s // 60} 分钟")


# 限流规则表：(endpoint, limit, window_seconds)
# endpoint 是自定义 tag，跟 URL 无强关联，方便同一 action 多入口共享计数
LIMITS: dict[str, tuple[int, int]] = {
    # (每窗口次数, 窗口秒数)
    "mail_send_min": (10, 60),
    "mail_send_hour": (100, 3600),
    "query_min": (30, 60),
    "query_hour": (500, 3600),
}


def check_rate_limit(user_id: str, endpoint: str) -> None:
    """检查 (user_id, endpoint) 是否超过 LIMITS 里对应的配额。

    这是 **硬限** —— 超了直接抛 RateLimitExceeded；调用方要抛 HTTP 429。
    每次检查会同步 INSERT 一条记录到 rate_limit_log。
    """
    if endpoint not in LIMITS:
        raise ValueError(f"unknown rate-limit endpoint: {endpoint}")
    limit, window_s = LIMITS[endpoint]
    now = now_ts()
    cutoff = now - window_s

    with get_conn() as conn:
        row = conn.execute(
            "SELECT COUNT(*) AS n FROM rate_limit_log "
            "WHERE user_id = ? AND endpoint = ? AND ts >= ?",
            (user_id, endpoint, cutoff),
        ).fetchone()
        count = int(row["n"]) if row else 0
        if count >= limit:
            raise RateLimitExceeded(endpoint, limit, window_s)
        conn.execute(
            "INSERT INTO rate_limit_log (user_id, endpoint, ts) VALUES (?, ?, ?)",
            (user_id, endpoint, now),
        )


def check_group(user_id: str, group: str) -> None:
    """便捷批量检查：一组 endpoint 都要查（如 mail_send_min + mail_send_hour）。

    group: 'mail_send' → 查 mail_send_min 和 mail_send_hour。
    """
    prefixes = {
        "mail_send": ["mail_send_min", "mail_send_hour"],
        "query": ["query_min", "query_hour"],
    }
    for ep in prefixes.get(group, []):
        check_rate_limit(user_id, ep)


def prune_old(older_than_days: int = 7) -> int:
    """删除旧日志。运维 / cron 定期调。返回删除的行数。"""
    cutoff = now_ts() - older_than_days * 86400
    with get_conn() as conn:
        cursor = conn.execute("DELETE FROM rate_limit_log WHERE ts < ?", (cutoff,))
        return cursor.rowcount
