"""
Unit tests for rate limiting middleware and per-endpoint rate limiter.
"""

import pytest
import time
from unittest.mock import MagicMock, AsyncMock

from app.middleware.rate_limiter import (
    RateLimiter,
    EndpointRateLimiter,
    get_user_identifier,
)


# ============================================================================
# RateLimiter
# ============================================================================


class TestRateLimiter:
    @pytest.mark.asyncio
    async def test_allows_within_limit(self):
        limiter = RateLimiter(requests_per_second=5, window_size=1)
        for _ in range(5):
            allowed, info = await limiter.is_allowed("user1")
            assert allowed is True

    @pytest.mark.asyncio
    async def test_blocks_over_limit(self):
        limiter = RateLimiter(requests_per_second=3, window_size=1)
        for _ in range(3):
            await limiter.is_allowed("user1")

        allowed, info = await limiter.is_allowed("user1")
        assert allowed is False
        assert info["remaining"] == 0
        assert info["retry_after"] == 1

    @pytest.mark.asyncio
    async def test_different_users_independent(self):
        limiter = RateLimiter(requests_per_second=2, window_size=1)
        for _ in range(2):
            await limiter.is_allowed("user1")

        # user1 is at limit
        allowed1, _ = await limiter.is_allowed("user1")
        assert allowed1 is False

        # user2 should still be allowed
        allowed2, _ = await limiter.is_allowed("user2")
        assert allowed2 is True

    @pytest.mark.asyncio
    async def test_info_fields(self):
        limiter = RateLimiter(requests_per_second=10, window_size=1)
        allowed, info = await limiter.is_allowed("user1")
        assert info["limit"] == 10
        assert info["remaining"] == 9
        assert "reset" in info


# ============================================================================
# EndpointRateLimiter
# ============================================================================


class TestEndpointRateLimiter:
    @pytest.mark.asyncio
    async def test_allows_within_limit(self):
        limiter = EndpointRateLimiter(max_requests=3, window_seconds=60)
        request = MagicMock()
        request.url.path = "/api/v1/terminal/deploy"
        request.state = MagicMock()
        request.state.user = MagicMock(id=1)
        request.headers = {}

        for _ in range(3):
            await limiter(request)  # should not raise

    @pytest.mark.asyncio
    async def test_blocks_over_limit(self):
        from fastapi import HTTPException
        limiter = EndpointRateLimiter(max_requests=2, window_seconds=60)
        request = MagicMock()
        request.url.path = "/api/v1/terminal/deploy"
        request.state = MagicMock()
        request.state.user = MagicMock(id=1)
        request.headers = {}

        await limiter(request)
        await limiter(request)

        with pytest.raises(HTTPException) as exc_info:
            await limiter(request)
        assert exc_info.value.status_code == 429


# ============================================================================
# get_user_identifier
# ============================================================================


class TestGetUserIdentifier:
    def test_authenticated_user(self):
        request = MagicMock()
        request.state.user = MagicMock(id=42)
        assert get_user_identifier(request) == "user_42"

    def test_anonymous_user_cloudflare(self):
        request = MagicMock()
        request.state = MagicMock(spec=[])  # no .user
        request.headers = {"CF-Connecting-IP": "1.2.3.4"}
        result = get_user_identifier(request)
        assert result == "ip_1.2.3.4"

    def test_anonymous_user_no_cf(self):
        request = MagicMock()
        request.state = MagicMock(spec=[])
        request.headers = {}
        result = get_user_identifier(request)
        assert result == "ip_unknown"
