"""Tests for rate limiting checkers (RATE002)."""

import pytest

from stablestack.checkers.rate_limiting import InMemoryRateLimiterChecker


class TestInMemoryRateLimiterChecker:
    """Tests for RATE002: in-memory-rate-limiter."""

    @pytest.fixture
    def checker(self):
        return InMemoryRateLimiterChecker()

    # ── Python detection ──────────────────────────────────────────────

    def test_detects_dict_rate_store(self, checker, temp_py_file):
        """Should flag dict used as rate limit store."""
        code = "request_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "RATE002"

    def test_detects_defaultdict_rate_store(self, checker, temp_py_file):
        """Should flag defaultdict used as rate limit store."""
        code = "from collections import defaultdict\nrate_limits = defaultdict(list)\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "RATE002"

    def test_detects_dict_call_rate_store(self, checker, temp_py_file):
        """Should flag dict() call used as rate limit store."""
        code = "throttle_map = dict()\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_limit_tracker_dict(self, checker, temp_py_file):
        """Should flag variable with 'limit' in name assigned to empty dict."""
        code = "limit_tracker = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_time_in_rate_function(self, checker, temp_py_file):
        """Should flag time.time() inside a rate-limit function."""
        code = (
            "def check_rate_limit(ip):\n"
            "    now = time.time()\n"
            "    return now\n"
        )
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_time_monotonic_in_throttle_fn(self, checker, temp_py_file):
        """Should flag time.monotonic() inside a throttle function."""
        code = (
            "def throttle_request(user_id):\n"
            "    ts = time.monotonic()\n"
            "    return ts\n"
        )
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_full_naive_rate_limiter(self, checker, temp_py_file):
        """Should flag a realistic naive rate limiter."""
        code = (
            "request_counts = {}\n"
            "\n"
            "def check_rate_limit(ip):\n"
            "    if request_counts.get(ip, 0) > 100:\n"
            "        return False\n"
            "    request_counts[ip] = request_counts.get(ip, 0) + 1\n"
            "    return True\n"
        )
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    # ── Python suppression ────────────────────────────────────────────

    def test_suppresses_with_redis_import(self, checker, temp_py_file):
        """Should not flag when redis is imported."""
        code = "import redis\nrequest_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_with_from_redis_import(self, checker, temp_py_file):
        """Should not flag with from redis import."""
        code = "from redis import Redis\nrequest_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_with_slowapi_import(self, checker, temp_py_file):
        """Should not flag when slowapi is imported."""
        code = "from slowapi import Limiter\nrequest_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_with_stablestack_rate_limiter(self, checker, temp_py_file):
        """Should not flag when stablestack_rate_limiter is used."""
        code = "from stablestack_rate_limiter import rate_limit\nrequest_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_noqa_comment(self, checker, temp_py_file):
        """Should not flag lines with noqa: RATE002."""
        code = "request_counts = {}  # noqa: RATE002\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_comment_lines(self, checker, temp_py_file):
        """Should not flag commented-out code."""
        code = "# request_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_flag_unrelated_dict(self, checker, temp_py_file):
        """Should not flag dicts without rate-limit-related names."""
        code = "user_preferences = {}\nconfig = dict()\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_flag_time_outside_rate_fn(self, checker, temp_py_file):
        """Should not flag time.time() outside rate-limit functions."""
        code = (
            "def process_data():\n"
            "    start = time.time()\n"
            "    return time.time() - start\n"
        )
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    # ── JavaScript/TypeScript detection ───────────────────────────────

    def test_detects_js_map_rate_store(self, checker, temp_ts_file):
        """Should flag new Map() as rate limit store in JS/TS."""
        code = "const rateLimitMap = new Map();\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "RATE002"

    def test_detects_js_object_rate_store(self, checker, temp_ts_file):
        """Should flag {} as rate limit store in JS/TS."""
        code = "const requestLimits = {};\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_let_throttle_map(self, checker, temp_ts_file):
        """Should flag let variable with throttle in name."""
        code = "let throttleStore = new Map();\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_setinterval_clear(self, checker, temp_ts_file):
        """Should flag setInterval clearing rate limit data."""
        code = "setInterval(() => rateLimitMap.clear(), 60000);\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_token_bucket_in_rate_context(self, checker, temp_ts_file):
        """Should flag token bucket vars when rate-limit context exists."""
        code = (
            "// Simple rate limiter\n"
            "let tokens = maxTokens;\n"
        )
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    # ── JavaScript/TypeScript suppression ─────────────────────────────

    def test_suppresses_js_with_ioredis(self, checker, temp_ts_file):
        """Should not flag when ioredis is imported."""
        code = "import Redis from 'ioredis';\nconst rateLimitMap = new Map();\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_js_with_upstash(self, checker, temp_ts_file):
        """Should not flag when @upstash/ratelimit is imported."""
        code = "import { Ratelimit } from '@upstash/ratelimit';\nconst requestCounts = {};\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_js_with_express_rate_limit(self, checker, temp_ts_file):
        """Should not flag when express-rate-limit is imported."""
        code = "import rateLimit from 'express-rate-limit';\nconst requestLimits = {};\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_js_noqa(self, checker, temp_ts_file):
        """Should not flag lines with noqa: RATE002."""
        code = "const rateLimitMap = new Map(); // noqa: RATE002\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_suppresses_js_comment(self, checker, temp_ts_file):
        """Should not flag commented-out code."""
        code = "// const rateLimitMap = new Map();\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_flag_unrelated_js_map(self, checker, temp_ts_file):
        """Should not flag Maps without rate-limit-related names."""
        code = "const userCache = new Map();\nconst config = {};\n"
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    # ── File extension support ────────────────────────────────────────

    def test_supports_jsx_files(self, checker, tmp_path):
        """Should check .jsx files."""
        code = "const rateLimitMap = new Map();\n"
        path = tmp_path / "test.jsx"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_supports_tsx_files(self, checker, tmp_path):
        """Should check .tsx files."""
        code = "const rateLimitMap = new Map();\n"
        path = tmp_path / "test.tsx"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    # ── Fix hint ──────────────────────────────────────────────────────

    def test_fix_hint_references_scaffold(self, checker, temp_py_file):
        """Fix hint should reference stablestack add-rate-limiter."""
        code = "request_counts = {}\n"
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert "stablestack add-rate-limiter" in findings[0].fix_hint
