"""Tests for rate_limiter module - Flask rate limiting."""

import pytest
from unittest.mock import patch
from flask import Flask, g

from local_deep_research.security.rate_limiter import (
    get_rate_limiter,
    init_rate_limiter,
    upload_rate_limit,
    RATE_LIMIT_FAIL_CLOSED,
)


@pytest.fixture
def app():
    """Create test Flask application."""
    app = Flask(__name__)
    app.config["TESTING"] = True
    return app


@pytest.fixture
def reset_limiter():
    """Reset global limiter state before and after tests."""
    import local_deep_research.security.rate_limiter as rl_module

    original_limiter = rl_module._limiter
    rl_module._limiter = None
    yield
    rl_module._limiter = original_limiter


class TestGetRateLimiter:
    """Tests for get_rate_limiter function."""

    def test_raises_when_not_initialized(self, reset_limiter):
        """Should raise RuntimeError when limiter not initialized."""
        with pytest.raises(RuntimeError, match="not initialized"):
            get_rate_limiter()

    def test_returns_limiter_when_initialized(self, app, reset_limiter):
        """Should return limiter after initialization."""
        init_rate_limiter(app)
        limiter = get_rate_limiter()
        assert limiter is not None


class TestInitRateLimiter:
    """Tests for init_rate_limiter function."""

    def test_initializes_limiter(self, app, reset_limiter):
        """Should initialize and return limiter."""
        limiter = init_rate_limiter(app)
        assert limiter is not None

    def test_can_get_limiter_after_init(self, app, reset_limiter):
        """Should be able to get limiter after initialization."""
        init_rate_limiter(app)
        limiter = get_rate_limiter()
        assert limiter is not None

    def test_headers_enabled(self, app, reset_limiter):
        """Should enable rate limit headers."""
        limiter = init_rate_limiter(app)
        # Limiter should have headers enabled
        assert limiter._headers_enabled is True

    def test_uses_memory_storage(self, app, reset_limiter):
        """Should use in-memory storage for development."""
        limiter = init_rate_limiter(app)
        # Check storage is configured (memory:// prefix in uri)
        assert limiter is not None


class TestUploadRateLimitDecorator:
    """Tests for upload_rate_limit decorator."""

    def test_returns_function_when_limiter_not_initialized(self, reset_limiter):
        """Should return function when limiter not initialized (fail-open)."""

        def my_upload():
            return "uploaded"

        # Without RATE_LIMIT_FAIL_CLOSED, should pass through
        with patch(
            "local_deep_research.security.rate_limiter.RATE_LIMIT_FAIL_CLOSED",
            False,
        ):
            decorated = upload_rate_limit(my_upload)
            # Should return the original function
            assert decorated() == "uploaded"

    def test_raises_when_fail_closed_and_not_initialized(self, reset_limiter):
        """Should raise RuntimeError when fail-closed and limiter not initialized."""

        def my_upload():
            return "uploaded"

        with patch(
            "local_deep_research.security.rate_limiter.RATE_LIMIT_FAIL_CLOSED",
            True,
        ):
            with pytest.raises(RuntimeError, match="not initialized"):
                upload_rate_limit(my_upload)

    def test_applies_rate_limit_when_initialized(self, app, reset_limiter):
        """Should apply rate limiting when limiter is initialized."""
        init_rate_limiter(app)

        def my_upload():
            return "uploaded"

        decorated = upload_rate_limit(my_upload)

        # Decorated function should be wrapped (not the original)
        assert decorated is not my_upload

    def test_passes_through_on_unexpected_error_fail_open(self, reset_limiter):
        """Should pass through on unexpected error when fail-open."""

        def my_upload():
            return "uploaded"

        # Mock get_rate_limiter to raise unexpected error
        with patch(
            "local_deep_research.security.rate_limiter.get_rate_limiter",
            side_effect=RuntimeError("not initialized"),
        ):
            with patch(
                "local_deep_research.security.rate_limiter.RATE_LIMIT_FAIL_CLOSED",
                False,
            ):
                decorated = upload_rate_limit(my_upload)
                # Should return original function
                assert decorated() == "uploaded"


class TestGetUserIdentifier:
    """Tests for user identifier function used by limiter."""

    def test_uses_username_when_authenticated(self, app, reset_limiter):
        """Should use username when user is authenticated."""
        with app.app_context():
            with patch(
                "local_deep_research.security.rate_limiter.get_remote_address",
                return_value="192.168.1.100",
            ):
                limiter = init_rate_limiter(app)

                # Simulate authenticated user
                g.current_user = "testuser"

                # Get the key_func and call it
                key = limiter._key_func()
                assert key == "user:testuser"

    def test_uses_ip_when_not_authenticated(self, app, reset_limiter):
        """Should use IP address when user is not authenticated."""
        with app.app_context():
            with patch(
                "local_deep_research.security.rate_limiter.get_remote_address",
                return_value="192.168.1.100",
            ):
                limiter = init_rate_limiter(app)

                # No authenticated user
                g.current_user = None

                # Get the key_func and call it
                key = limiter._key_func()
                assert key == "ip:192.168.1.100"


class TestRateLimitFailClosed:
    """Tests for RATE_LIMIT_FAIL_CLOSED configuration."""

    def test_default_is_fail_open(self):
        """RATE_LIMIT_FAIL_CLOSED should default to False."""
        # Default should be fail-open for easier development
        # Note: actual value depends on environment, testing the behavior
        assert isinstance(RATE_LIMIT_FAIL_CLOSED, bool)

    def test_environment_variable_parsing(self):
        """Should parse RATE_LIMIT_FAIL_CLOSED from environment."""
        import os

        with patch.dict(os.environ, {"RATE_LIMIT_FAIL_CLOSED": "true"}):
            # Re-import to pick up environment variable
            import importlib
            import local_deep_research.security.rate_limiter as rl

            importlib.reload(rl)
            assert rl.RATE_LIMIT_FAIL_CLOSED is True

        # Reset
        with patch.dict(os.environ, {"RATE_LIMIT_FAIL_CLOSED": "false"}):
            importlib.reload(rl)
            assert rl.RATE_LIMIT_FAIL_CLOSED is False
