"""
Tests for the rate limiting middleware.

This module contains tests for the RateLimitMiddleware functionality.
"""

import unittest
from unittest.mock import Mock, patch

from django.http import HttpResponse
from django.test import RequestFactory, TestCase, override_settings

from django_smart_ratelimit.middleware import (
    RateLimitMiddleware,
    default_key_function,
    user_key_function,
)
from tests.utils import BaseBackendTestCase, create_test_user

# Check if redis is available
try:
    import redis as redis_module  # noqa: F401

    HAS_REDIS = True
except ImportError:
    HAS_REDIS = False


class RateLimitMiddlewareTests(BaseBackendTestCase):
    """Tests for the rate limiting middleware."""

    def setUp(self):
        """Set up test fixtures."""
        super().setUp()
        self.factory = RequestFactory()
        self.user = create_test_user()

    def test_default_key_function(self):
        """Test the default key function uses IP address."""
        _request = self.factory.get("/")
        key = default_key_function(_request)
        self.assertEqual(key, "middleware:127.0.0.1")

    def test_default_key_function_with_forwarded_for(self):
        """Test default key function with X-Forwarded-For header."""
        _request = self.factory.get("/", HTTP_X_FORWARDED_FOR="192.168.1.1, 10.0.0.1")
        key = default_key_function(_request)
        self.assertEqual(key, "middleware:192.168.1.1")

    def test_user_key_function_authenticated(self):
        """Test user key function with authenticated user."""
        _request = self.factory.get("/")
        _request.user = self.user
        key = user_key_function(_request)
        self.assertEqual(key, f"middleware:user:{self.user.id}")

    def test_user_key_function_anonymous(self):
        """Test user key function with anonymous user."""
        _request = self.factory.get("/")
        _request.user = Mock()
        _request.user.is_authenticated = False
        key = user_key_function(_request)
        self.assertEqual(key, "middleware:127.0.0.1")

    @override_settings(
        RATELIMIT_MIDDLEWARE={
            "DEFAULT_RATE": "10/m",
            "BACKEND": "redis",
            "BLOCK": True,
            "SKIP_PATHS": ["/admin/", "/health/"],
            "RATE_LIMITS": {
                "/api/": "100/h",
                "/auth/": "5/m",
            },
        }
    )
    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_initialization(self, mock_get_backend):
        """Test middleware initialization with configuration."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        middleware = RateLimitMiddleware(get_response)

        self.assertEqual(middleware.default_rate, "10/m")
        self.assertEqual(middleware.backend_name, "redis")
        self.assertTrue(middleware.block)
        self.assertEqual(middleware.skip_paths, ["/admin/", "/health/"])
        self.assertEqual(middleware.rate_limits["/api/"], "100/h")
        self.assertEqual(middleware.rate_limits["/auth/"], "5/m")

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_skips_configured_paths(self, mock_get_backend):
        """Test middleware skips paths configured in SKIP_PATHS."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={"SKIP_PATHS": ["/admin/", "/health/"]}
        ):
            middleware = RateLimitMiddleware(get_response)

            # Test admin path is skipped
            _request = self.factory.get("/admin/users/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            mock_backend.incr.assert_not_called()

            # Test health path is skipped
            _request = self.factory.get("/health/check")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            mock_backend.incr.assert_not_called()

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_applies_path_specific_rates(self, mock_get_backend):
        """Test middleware applies different rates for different paths."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={
                "DEFAULT_RATE": "10/m",
                "RATE_LIMITS": {
                    "/api/": "100/h",
                    "/auth/": "5/m",
                },
            }
        ):
            middleware = RateLimitMiddleware(get_response)

            # Test API path gets higher rate
            _request = self.factory.get("/api/users/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.headers["X-RateLimit-Limit"], "100")

            # Test auth path gets lower rate
            _request = self.factory.get("/auth/login/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.headers["X-RateLimit-Limit"], "5")

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_blocks_when_limit_exceeded(self, mock_get_backend):
        """Test middleware blocks requests when limit is exceeded."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 11  # Exceeds limit of 10
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={"DEFAULT_RATE": "10/m", "BLOCK": True}
        ):
            middleware = RateLimitMiddleware(get_response)

            _request = self.factory.get("/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 429)
            self.assertIn("X-RateLimit-Limit", response.headers)
            self.assertEqual(response.headers["X-RateLimit-Remaining"], "0")

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_allows_when_within_limit(self, mock_get_backend):
        """Test middleware allows requests when within limit."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 5  # Within limit of 10
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={"DEFAULT_RATE": "10/m", "BLOCK": True}
        ):
            middleware = RateLimitMiddleware(get_response)

            _request = self.factory.get("/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.headers["X-RateLimit-Limit"], "10")
            self.assertEqual(response.headers["X-RateLimit-Remaining"], "5")

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_with_custom_key_function(self, mock_get_backend):
        """Test middleware with custom key function."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={
                "DEFAULT_RATE": "10/m",
                "KEY_FUNCTION": ("django_smart_ratelimit.middleware.user_key_function"),
            }
        ):
            middleware = RateLimitMiddleware(get_response)

            _request = self.factory.get("/")
            _request.user = self.user
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            # Verify the correct key was used
            mock_backend.incr.assert_called_once()
            call_args = mock_backend.incr.call_args[0]
            self.assertEqual(call_args[0], f"middleware:user:{self.user.id}")

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_parse_rate_error(self, mock_get_backend):
        """Test middleware handles invalid rate format during _request."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(RATELIMIT_MIDDLEWARE={"DEFAULT_RATE": "invalid_rate"}):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/")

            # The error should happen during _request processing
            with self.assertRaises(Exception):
                middleware(_request)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_load_invalid_key_function(self, mock_get_backend):
        """Test middleware handles invalid key function."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={"KEY_FUNCTION": "invalid.module.function"}
        ):
            with self.assertRaises(Exception):
                RateLimitMiddleware(get_response)


class RateLimitMiddlewareIntegrationTests(TestCase):
    """Integration tests for the rate limiting middleware."""

    def setUp(self):
        self.factory = RequestFactory()

    @unittest.skipUnless(HAS_REDIS, "redis package not installed")
    @patch("django_smart_ratelimit.backends.redis_backend.redis")
    def test_middleware_with_redis_backend(self, mock_redis_module):
        """Test middleware with Redis backend integration."""
        # Mock Redis client
        mock_redis_client = Mock()
        mock_redis_module.Redis.return_value = mock_redis_client
        mock_redis_client.ping.return_value = True
        mock_redis_client.script_load.return_value = "script_sha"
        mock_redis_client.evalsha.return_value = 1

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            RATELIMIT_MIDDLEWARE={"DEFAULT_RATE": "5/s", "BACKEND": "redis"}
        ):
            middleware = RateLimitMiddleware(get_response)

            _request = self.factory.get("/")
            response = middleware(_request)

            self.assertEqual(response.status_code, 200)
            self.assertIn("X-RateLimit-Limit", response.headers)
            self.assertEqual(response.headers["X-RateLimit-Limit"], "5")


class RateLimitMiddlewareStaticMediaSecurityTests(TestCase):
    """
    Security tests for static/media URL handling.

    These tests verify that the middleware correctly respects Django's
    STATIC_URL and MEDIA_URL settings instead of hardcoding paths.

    Security Note: This prevents rate limit bypass when custom prefixes
    are configured for static and media files.
    """

    def setUp(self):
        self.factory = RequestFactory()

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_skips_default_static_url(self, mock_get_backend):
        """Test middleware skips default /static/ path."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(STATIC_URL="/static/", RATELIMIT_MIDDLEWARE={}):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/static/css/main.css")
            response = middleware(_request)

            # Static files should be skipped - backend should NOT be called
            mock_backend.incr.assert_not_called()
            self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_skips_custom_static_url(self, mock_get_backend):
        """
        Test middleware skips custom STATIC_URL.

        Security: Ensures rate limit bypass is prevented when
        STATIC_URL is customized (e.g., /assets/, /cdn/).
        """
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(STATIC_URL="/assets/", RATELIMIT_MIDDLEWARE={}):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/assets/js/app.js")
            response = middleware(_request)

            # Custom static URL should be skipped
            mock_backend.incr.assert_not_called()
            self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_skips_default_media_url(self, mock_get_backend):
        """Test middleware skips default /media/ path."""
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(MEDIA_URL="/media/", RATELIMIT_MIDDLEWARE={}):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/media/uploads/image.png")
            response = middleware(_request)

            # Media files should be skipped
            mock_backend.incr.assert_not_called()
            self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_skips_custom_media_url(self, mock_get_backend):
        """
        Test middleware skips custom MEDIA_URL.

        Security: Ensures rate limit bypass is prevented when
        MEDIA_URL is customized (e.g., /uploads/, /files/).
        """
        mock_backend = Mock()
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(MEDIA_URL="/uploads/", RATELIMIT_MIDDLEWARE={}):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/uploads/documents/report.pdf")
            response = middleware(_request)

            # Custom media URL should be skipped
            mock_backend.incr.assert_not_called()
            self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_does_not_skip_non_static_paths(self, mock_get_backend):
        """Test middleware applies rate limiting to non-static paths."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        with override_settings(
            STATIC_URL="/static/", MEDIA_URL="/media/", RATELIMIT_MIDDLEWARE={}
        ):
            middleware = RateLimitMiddleware(get_response)
            _request = self.factory.get("/api/users/")
            response = middleware(_request)

            # Non-static paths should be rate limited
            mock_backend.incr.assert_called_once()
            self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    def test_middleware_security_custom_prefix_bypass_prevention(
        self, mock_get_backend
    ):
        """
        Security test: Verify attacker cannot bypass rate limits
        by exploiting custom static/media prefixes.

        Scenario: STATIC_URL is /cdn/, attacker tries /static/ hoping
        it's hardcoded and skipped.
        """
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        # Configure custom STATIC_URL
        with override_settings(STATIC_URL="/cdn/", RATELIMIT_MIDDLEWARE={}):
            middleware = RateLimitMiddleware(get_response)

            # Attacker tries default /static/ path (should be rate limited)
            _request = self.factory.get("/static/exploit.js")
            response = middleware(_request)

            # Path should NOT be skipped (it's not the configured STATIC_URL)
            mock_backend.incr.assert_called_once()
            self.assertEqual(response.status_code, 200)


class RateLimitMiddlewareEnableSettingTests(BaseBackendTestCase):
    """Tests for RATELIMIT_ENABLE setting in middleware."""

    def setUp(self):
        """Set up test fixtures."""
        super().setUp()
        self.factory = RequestFactory()

    @patch("django_smart_ratelimit.middleware.get_backend")
    @override_settings(RATELIMIT_ENABLE=False, RATELIMIT_MIDDLEWARE={})
    def test_middleware_disabled_when_ratelimit_enable_false(self, mock_get_backend):
        """Test that middleware is bypassed when RATELIMIT_ENABLE=False."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        middleware = RateLimitMiddleware(get_response)
        _request = self.factory.get("/api/test/")
        response = middleware(_request)

        # Backend should NOT be called when rate limiting is disabled
        mock_backend.incr.assert_not_called()
        self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    @override_settings(RATELIMIT_ENABLE=True, RATELIMIT_MIDDLEWARE={})
    def test_middleware_enabled_when_ratelimit_enable_true(self, mock_get_backend):
        """Test that middleware applies rate limiting when RATELIMIT_ENABLE=True."""
        mock_backend = Mock()
        mock_backend.incr.return_value = 1
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        middleware = RateLimitMiddleware(get_response)
        _request = self.factory.get("/api/test/")
        response = middleware(_request)

        # Backend should be called when rate limiting is enabled
        mock_backend.incr.assert_called_once()
        self.assertEqual(response.status_code, 200)

    @patch("django_smart_ratelimit.middleware.get_backend")
    @override_settings(RATELIMIT_ENABLE=False, RATELIMIT_MIDDLEWARE={})
    def test_middleware_disabled_no_429_response(self, mock_get_backend):
        """Test that disabled middleware never returns 429 even for heavy load."""
        mock_backend = Mock()
        # Simulate limit exceeded
        mock_backend.incr.return_value = 1000
        mock_get_backend.return_value = mock_backend

        def get_response(_request):
            return HttpResponse("OK")

        middleware = RateLimitMiddleware(get_response)

        # Make many requests - all should succeed
        for _ in range(10):
            _request = self.factory.get("/api/test/")
            response = middleware(_request)
            self.assertEqual(response.status_code, 200)

        # Backend should never be called
        mock_backend.incr.assert_not_called()
