"""Tests for rate limiting."""

import time

import pytest

from rpipe.rate_limiter import RateLimiter, RateLimitConfig


class TestRateLimiterBasic:
    """Test basic rate limiter functionality."""

    def test_create_limiter(self):
        """Create a rate limiter."""
        limiter = RateLimiter()
        assert limiter.config.requests_per_second == 10.0
        assert limiter.config.max_burst == 100
        assert limiter.config.enforce is True

    def test_custom_config(self):
        """Create limiter with custom config."""
        config = RateLimitConfig(
            requests_per_second=5.0,
            max_burst=50,
            enforce=True,
        )
        limiter = RateLimiter(config)
        assert limiter.config.requests_per_second == 5.0
        assert limiter.config.max_burst == 50

    def test_is_allowed_initial(self):
        """Initial request is allowed."""
        limiter = RateLimiter()
        assert limiter.is_allowed("client1") is True

    def test_multiple_rapid_requests(self):
        """Multiple rapid requests are allowed up to burst limit."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=5, enforce=True)
        limiter = RateLimiter(config)

        # First 5 requests should be allowed (burst capacity)
        for _ in range(5):
            assert limiter.is_allowed("client1") is True

        # 6th request should be denied (need time for refill)
        assert limiter.is_allowed("client1") is False

    def test_refill_after_time(self):
        """Tokens refill after time passes."""
        config = RateLimitConfig(requests_per_second=10.0, max_burst=10, enforce=True)
        limiter = RateLimiter(config)

        # Exhaust burst
        for _ in range(10):
            assert limiter.is_allowed("client1") is True

        # Request denied
        assert limiter.is_allowed("client1") is False

        # Wait for tokens to refill (0.1 second = 1 token at 10 req/sec)
        time.sleep(0.11)

        # One request should now be allowed
        assert limiter.is_allowed("client1") is True

    def test_enforcement_disabled(self):
        """Rate limit not enforced when disabled."""
        config = RateLimitConfig(enforce=False)
        limiter = RateLimiter(config)

        # Should be allowed unlimited
        for _ in range(1000):
            assert limiter.is_allowed("client1") is True


class TestRateLimiterMultiClient:
    """Test rate limiter with multiple clients."""

    def test_per_client_buckets(self):
        """Each client has independent rate limit."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=2, enforce=True)
        limiter = RateLimiter(config)

        # Client 1 exhausts burst
        for _ in range(2):
            assert limiter.is_allowed("client1") is True
        assert limiter.is_allowed("client1") is False

        # Client 2 should still be allowed
        for _ in range(2):
            assert limiter.is_allowed("client2") is True
        assert limiter.is_allowed("client2") is False

    def test_different_clients_dont_share_quota(self):
        """Clients don't interfere with each other."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=1, enforce=True)
        limiter = RateLimiter(config)

        assert limiter.is_allowed("client1") is True
        assert limiter.is_allowed("client2") is True

        # Both should now be at rate limit
        assert limiter.is_allowed("client1") is False
        assert limiter.is_allowed("client2") is False


class TestMultiTokenRequests:
    """Test requesting multiple tokens at once."""

    def test_try_acquire_single_token(self):
        """Acquire single token."""
        config = RateLimitConfig(max_burst=10, enforce=True)
        limiter = RateLimiter(config)

        assert limiter.try_acquire("client1", 1.0) is True
        assert limiter.try_acquire("client1", 1.0) is True

    def test_try_acquire_multiple_tokens(self):
        """Acquire multiple tokens at once."""
        config = RateLimitConfig(max_burst=10, enforce=True)
        limiter = RateLimiter(config)

        # Acquire 5 tokens
        assert limiter.try_acquire("client1", 5.0) is True

        # Should have 5 left
        for _ in range(5):
            assert limiter.try_acquire("client1", 1.0) is True

        # Should be exhausted
        assert limiter.try_acquire("client1", 1.0) is False

    def test_try_acquire_insufficient_tokens(self):
        """Acquire fails if insufficient tokens."""
        config = RateLimitConfig(max_burst=5, enforce=True)
        limiter = RateLimiter(config)

        assert limiter.try_acquire("client1", 10.0) is False  # Need more than max burst
        assert limiter.try_acquire("client1", 5.0) is True   # Exactly max burst

    def test_invalid_token_count(self):
        """Error on invalid token count."""
        limiter = RateLimiter()

        with pytest.raises(ValueError):
            limiter.try_acquire("client1", 0.0)

        with pytest.raises(ValueError):
            limiter.try_acquire("client1", -1.0)


class TestStatusAndReset:
    """Test status queries and reset operations."""

    def test_get_status(self):
        """Get rate limit status for client."""
        config = RateLimitConfig(requests_per_second=10.0, max_burst=100, enforce=True)
        limiter = RateLimiter(config)

        limiter.is_allowed("client1")  # Create bucket

        status = limiter.get_status("client1")
        assert status["client_id"] == "client1"
        assert status["max_burst"] == 100
        assert status["refill_rate_per_second"] == 10.0
        assert status["enforcement_enabled"] is True

    def test_status_shows_tokens(self):
        """Status shows current token count."""
        config = RateLimitConfig(max_burst=10, enforce=True)
        limiter = RateLimiter(config)

        limiter.is_allowed("client1")
        status = limiter.get_status("client1")
        assert status["tokens_available"] < 10  # One was consumed

    def test_status_next_available_time(self):
        """Status shows when next request will be available."""
        config = RateLimitConfig(requests_per_second=100.0, max_burst=1, enforce=True)
        limiter = RateLimiter(config)

        # Exhaust bucket
        limiter.is_allowed("client1")

        status = limiter.get_status("client1")
        now = time.time()

        # Next available should be in the future
        assert status["next_request_available_at"] > now

    def test_reset_client(self):
        """Reset bucket for specific client."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=3, enforce=True)
        limiter = RateLimiter(config)

        # Exhaust client1
        for _ in range(3):
            limiter.is_allowed("client1")
        assert limiter.is_allowed("client1") is False

        # Reset client1
        limiter.reset_client("client1")

        # Should be allowed again
        for _ in range(3):
            assert limiter.is_allowed("client1") is True

    def test_reset_all(self):
        """Reset all client buckets."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=2, enforce=True)
        limiter = RateLimiter(config)

        # Exhaust both clients
        for _ in range(2):
            limiter.is_allowed("client1")
            limiter.is_allowed("client2")

        assert limiter.is_allowed("client1") is False
        assert limiter.is_allowed("client2") is False

        # Reset all
        limiter.reset_all()

        # Both should be allowed
        assert limiter.is_allowed("client1") is True
        assert limiter.is_allowed("client2") is True

    def test_get_all_clients(self):
        """Get status for all clients."""
        limiter = RateLimiter()

        limiter.is_allowed("client1")
        limiter.is_allowed("client2")

        all_status = limiter.get_all_clients()
        assert len(all_status) == 2
        assert "client1" in all_status
        assert "client2" in all_status

    def test_set_enforce(self):
        """Enable/disable enforcement."""
        config = RateLimitConfig(max_burst=1, enforce=True)
        limiter = RateLimiter(config)

        # Exhaust bucket
        limiter.is_allowed("client1")
        assert limiter.is_allowed("client1") is False

        # Disable enforcement
        limiter.set_enforce(False)

        # Should be allowed now
        assert limiter.is_allowed("client1") is True


class TestErrorHandling:
    """Test error handling."""

    def test_empty_client_id(self):
        """Error on empty client ID."""
        limiter = RateLimiter()

        with pytest.raises(ValueError):
            limiter.is_allowed("")

        with pytest.raises(ValueError):
            limiter.get_status("")


class TestRateLimitingScenarios:
    """Test real-world rate limiting scenarios."""

    def test_steady_rate_limiting(self):
        """Steady stream of requests respects rate."""
        config = RateLimitConfig(requests_per_second=5.0, max_burst=5, enforce=True)
        limiter = RateLimiter(config)

        # First 5 requests use burst
        for _ in range(5):
            assert limiter.is_allowed("client1") is True

        # Next requests denied
        assert limiter.is_allowed("client1") is False
        assert limiter.is_allowed("client1") is False

        # After 0.2 seconds, 1 more request should be allowed
        time.sleep(0.21)
        assert limiter.is_allowed("client1") is True

    def test_bursty_workload(self):
        """Burst capacity handles temporary spikes."""
        config = RateLimitConfig(requests_per_second=1.0, max_burst=10, enforce=True)
        limiter = RateLimiter(config)

        # Spike: 10 requests
        for _ in range(10):
            assert limiter.is_allowed("spike_client") is True

        # Should be rate limited after
        assert limiter.is_allowed("spike_client") is False

        # Wait for refill
        time.sleep(1.1)

        # Should allow again
        assert limiter.is_allowed("spike_client") is True
