# -*- coding: utf-8 -*-
"""RateLimiter 单元测试。

被测代码：apps/backend/app/ai/rate_limiter.py
纯内存测试，不涉及 DB/网络。
"""

from __future__ import annotations

import time
from unittest.mock import patch

from app.ai.rate_limiter import RateLimiter


class TestCheckUserRate:
    """App1 每用户每分钟限流。"""

    def test_allows_under_limit(self):
        rl = RateLimiter()
        for _ in range(10):
            assert rl.check_user_rate("u1", limit=10) is True

    def test_rejects_at_limit(self):
        rl = RateLimiter()
        for _ in range(10):
            rl.check_user_rate("u1", limit=10)
        assert rl.check_user_rate("u1", limit=10) is False

    def test_different_users_independent(self):
        rl = RateLimiter()
        for _ in range(10):
            rl.check_user_rate("u1", limit=10)
        # u1 已满，u2 不受影响
        assert rl.check_user_rate("u1", limit=10) is False
        assert rl.check_user_rate("u2", limit=10) is True

    def test_window_expiry_allows_again(self):
        """窗口过期后，历史请求不影响当前判断。"""
        rl = RateLimiter()
        base = time.monotonic()

        with patch("app.ai.rate_limiter.time.monotonic", return_value=base):
            for _ in range(10):
                rl.check_user_rate("u1", limit=10, window_seconds=60)

        # 61 秒后，窗口内无请求
        with patch("app.ai.rate_limiter.time.monotonic", return_value=base + 61):
            assert rl.check_user_rate("u1", limit=10, window_seconds=60) is True


class TestCheckStoreRate:
    """App2~8 每门店每小时限流。"""

    def test_allows_under_limit(self):
        rl = RateLimiter()
        for _ in range(5):
            assert rl.check_store_rate(123, limit=5) is True

    def test_rejects_at_limit(self):
        rl = RateLimiter()
        for _ in range(5):
            rl.check_store_rate(123, limit=5)
        assert rl.check_store_rate(123, limit=5) is False

    def test_different_stores_independent(self):
        rl = RateLimiter()
        for _ in range(5):
            rl.check_store_rate(100, limit=5)
        assert rl.check_store_rate(100, limit=5) is False
        assert rl.check_store_rate(200, limit=5) is True

    def test_site_id_int_works(self):
        """site_id 为 int，内部转 str 存储。"""
        rl = RateLimiter()
        assert rl.check_store_rate(2790685415443269, limit=100) is True

    def test_window_expiry_allows_again(self):
        rl = RateLimiter()
        base = time.monotonic()

        with patch("app.ai.rate_limiter.time.monotonic", return_value=base):
            for _ in range(100):
                rl.check_store_rate(123, limit=100, window_seconds=3600)

        # 3601 秒后
        with patch("app.ai.rate_limiter.time.monotonic", return_value=base + 3601):
            assert rl.check_store_rate(123, limit=100, window_seconds=3600) is True


class TestRejectedRequestNotRecorded:
    """被拒绝的请求不应记录时间戳（不占用窗口配额）。"""

    def test_rejected_user_request_not_counted(self):
        rl = RateLimiter()
        for _ in range(3):
            rl.check_user_rate("u1", limit=3)
        # 连续拒绝不应增加窗口内计数
        rl.check_user_rate("u1", limit=3)
        rl.check_user_rate("u1", limit=3)
        assert len(rl._user_windows["u1"]) == 3

    def test_rejected_store_request_not_counted(self):
        rl = RateLimiter()
        for _ in range(3):
            rl.check_store_rate(1, limit=3)
        rl.check_store_rate(1, limit=3)
        rl.check_store_rate(1, limit=3)
        assert len(rl._store_windows["1"]) == 3
