"""
Protocol-based interface tests for SettingsManager.

These tests define and verify the contract that SettingsManager must fulfill
for consumers like rag_routes.py. Using Python's Protocol (PEP 544) enables:
- Static type checking with mypy to catch wrong imports at development time
- Runtime verification with @runtime_checkable
- Clear documentation of required interface

This prevents regressions like issue #1877 where the wrong SettingsManager
was imported, causing 'object has no attribute' errors at runtime.
"""

from typing import Any, Protocol, runtime_checkable
from unittest.mock import Mock


@runtime_checkable
class RagRoutesSettingsProtocol(Protocol):
    """
    Protocol defining the SettingsManager interface required by rag_routes.py.

    This protocol specifies the exact methods that rag_routes.py calls on
    SettingsManager. Any class used as a SettingsManager in rag_routes.py
    MUST implement all these methods.

    Usage in rag_routes.py:
    - get_setting(): Called throughout for retrieving settings
    - get_bool_setting(): Called at lines 133, 2185, 2309 for boolean settings
    - get_settings_snapshot(): Called at line 2193 for background thread operations
    """

    def get_setting(
        self, key: str, default: Any = None, check_env: bool = True
    ) -> Any:
        """Retrieve a setting value by key."""
        ...

    def get_bool_setting(self, key: str, default: bool = False) -> bool:
        """Retrieve a setting value as a boolean."""
        ...

    def get_settings_snapshot(self) -> dict[str, Any]:
        """Get a snapshot of all settings as a flat dictionary."""
        ...


class TestSettingsManagerProtocolCompliance:
    """
    Tests verifying SettingsManager implements the required protocol.

    These tests use Python's @runtime_checkable Protocol to verify that
    the SettingsManager class implements all required methods with the
    correct signatures.
    """

    def test_settings_manager_implements_protocol(self):
        """
        Verify settings.manager.SettingsManager implements protocol.

        This is the single unified SettingsManager used throughout the app.
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        manager = SettingsManager()

        # Runtime protocol check - this verifies the class has all required methods
        assert isinstance(manager, RagRoutesSettingsProtocol), (
            "settings.manager.SettingsManager must implement "
            "RagRoutesSettingsProtocol. Missing methods will cause runtime errors "
            "in rag_routes.py. See issue #1877."
        )


class TestSettingsManagerImportIdentity:
    """
    Tests verifying the correct SettingsManager is imported in rag_routes.py.

    These tests check that rag_routes.py imports SettingsManager from
    settings.manager (the unified implementation).
    """

    def test_rag_routes_imports_correct_settings_manager(self):
        """
        Verify rag_routes.py imports SettingsManager from settings.manager.

        This test checks the actual imported class identity, ensuring that
        rag_routes.py uses the correct SettingsManager that has all required
        methods (get_bool_setting, get_settings_snapshot).
        """
        from local_deep_research.research_library.routes import rag_routes
        from local_deep_research.settings.manager import (
            SettingsManager as ExpectedManager,
        )

        # Get the SettingsManager class that rag_routes is using
        # It should be imported at module level or used in functions
        assert hasattr(rag_routes, "SettingsManager"), (
            "rag_routes.py should have SettingsManager available. "
            "Check the import statements."
        )

        # Verify it's the correct class
        assert rag_routes.SettingsManager is ExpectedManager, (
            f"rag_routes.py imports SettingsManager from wrong module. "
            f"Expected: {ExpectedManager.__module__}.{ExpectedManager.__name__}, "
            f"Got: {rag_routes.SettingsManager.__module__}.{rag_routes.SettingsManager.__name__}. "
            f"This will cause 'has no attribute' errors. See issue #1877."
        )

    def test_rag_routes_settings_manager_has_required_methods(self):
        """
        Verify the SettingsManager in rag_routes has all methods it calls.

        This is a defense-in-depth test that checks the imported class
        has all the methods that rag_routes.py actually calls.
        """
        from local_deep_research.research_library.routes import rag_routes

        manager_class = rag_routes.SettingsManager
        manager = manager_class()

        # Methods called in rag_routes.py
        required_methods = [
            ("get_setting", True),  # (method_name, is_callable)
            ("get_bool_setting", True),
            ("get_settings_snapshot", True),
        ]

        for method_name, should_be_callable in required_methods:
            assert hasattr(manager, method_name), (
                f"rag_routes.py's SettingsManager missing '{method_name}'. "
                f"This method is called in rag_routes.py and will cause "
                f"AttributeError at runtime. See issue #1877."
            )

            if should_be_callable:
                attr = getattr(manager, method_name)
                assert callable(attr), (
                    f"rag_routes.py's SettingsManager.{method_name} is not callable. "
                    f"Expected a method, got {type(attr)}."
                )


class TestSettingsManagerMethodSignatures:
    """
    Tests verifying method signatures match expected usage patterns.

    These tests ensure that the methods not only exist but accept the
    arguments that rag_routes.py passes to them.
    """

    def test_get_bool_setting_accepts_key_and_default(self):
        """
        Verify get_bool_setting accepts (key, default) arguments.

        rag_routes.py calls: settings.get_bool_setting("key", True)
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        # This should not raise TypeError
        result = manager.get_bool_setting(
            "research_library.auto_index_enabled", True
        )

        assert isinstance(result, bool)

    def test_get_settings_snapshot_returns_mutable_dict(self):
        """
        Verify get_settings_snapshot returns a dict that can be modified.

        rag_routes.py modifies the snapshot: snapshot["_username"] = username
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.all.return_value = []
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        snapshot = manager.get_settings_snapshot()

        # Should be a dict
        assert isinstance(snapshot, dict)

        # Should be mutable (rag_routes.py adds keys to it)
        snapshot["_username"] = "testuser"
        snapshot["_db_password"] = "testpass"

        assert snapshot["_username"] == "testuser"
        assert snapshot["_db_password"] == "testpass"

    def test_get_setting_accepts_check_env_parameter(self):
        """
        Verify get_setting accepts check_env parameter.

        rag_routes.py calls: settings.get_setting("key", default, check_env=True)
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        # These should not raise TypeError
        result1 = manager.get_setting("some.key", "default", check_env=True)
        result2 = manager.get_setting("some.key", "default", check_env=False)

        # Should return the default when key not found
        assert result1 == "default"
        assert result2 == "default"


class TestSettingsManagerHasAllMethods:
    """
    Tests verifying that the unified SettingsManager has all required methods.
    """

    def test_settings_manager_has_all_methods(self):
        """Verify settings.manager.SettingsManager has all expected methods."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        manager = SettingsManager()

        expected_methods = [
            "get_setting",
            "get_bool_setting",
            "get_settings_snapshot",
            "get_all_settings",
            "set_setting",
            "delete_setting",
        ]

        for method in expected_methods:
            assert hasattr(manager, method), (
                f"settings.manager.SettingsManager should have {method}"
            )


class TestProtocolUsageInTypeChecking:
    """
    Tests demonstrating how to use the protocol for type checking.

    These tests show patterns that can be used elsewhere in the codebase
    to ensure type safety when working with SettingsManager.
    """

    def test_function_accepting_protocol(self):
        """
        Demonstrate a function that accepts any SettingsManager implementing protocol.

        This pattern can be used in rag_routes.py to ensure type safety:

        def get_rag_service(settings: RagRoutesSettingsProtocol) -> RAGService:
            enabled = settings.get_bool_setting("auto_index", True)
            ...
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        def requires_protocol(settings: RagRoutesSettingsProtocol) -> bool:
            """Function that requires the protocol."""
            return settings.get_bool_setting("test.key", False)

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        # This should work because SettingsManager implements the protocol
        result = requires_protocol(manager)
        assert isinstance(result, bool)

    def test_protocol_enables_mock_verification(self):
        """
        Demonstrate using protocol to create properly-shaped mocks.

        When mocking SettingsManager, the protocol ensures mocks have
        all required methods.
        """

        def create_mock_settings() -> RagRoutesSettingsProtocol:
            """Create a mock that satisfies the protocol."""
            mock = Mock(spec=RagRoutesSettingsProtocol)
            mock.get_setting.return_value = "test_value"
            mock.get_bool_setting.return_value = True
            mock.get_settings_snapshot.return_value = {"key": "value"}
            return mock

        mock_settings = create_mock_settings()

        # Verify the mock satisfies the protocol
        assert hasattr(mock_settings, "get_setting")
        assert hasattr(mock_settings, "get_bool_setting")
        assert hasattr(mock_settings, "get_settings_snapshot")

        # Use the mock
        assert mock_settings.get_bool_setting("test", False) is True
        assert mock_settings.get_settings_snapshot() == {"key": "value"}
