"""Integration tests for chat-mode settings extraction.

Replaces three earlier tests that only asserted on locally-constructed
dict literals (no production-code import) with checks that actually
exercise the chat route's settings helper and reproduce the snapshot
field-extraction the research pipeline performs.

These do NOT spin up a full DB — ``_load_settings`` is patched to
return a controlled snapshot and the extraction is asserted against
the same field names the production research path reads.
"""

from unittest.mock import MagicMock, patch


class TestLoadSettingsHelper:
    """Tests for the ``chat/routes.py::_load_settings`` helper."""

    def test_load_settings_bypasses_cache(self):
        """``_load_settings`` must call ``get_all_settings(bypass_cache=True)``
        so a UI setting change just before the chat send takes effect on
        the very next research run (matches the behaviour of
        ``research_routes.start_research``)."""
        from src.local_deep_research.chat import routes as chat_routes

        with (
            patch.object(chat_routes, "get_user_db_session") as mock_get_db,
            patch.object(chat_routes, "SettingsManager") as mock_manager_cls,
        ):
            mock_db = MagicMock()
            mock_get_db.return_value.__enter__.return_value = mock_db
            mock_manager = MagicMock()
            mock_manager.get_all_settings.return_value = {
                "llm.provider": {"value": "ollama"}
            }
            mock_manager_cls.return_value = mock_manager

            snapshot = chat_routes._load_settings("alice")

        mock_manager_cls.assert_called_once_with(db_session=mock_db)
        mock_manager.get_all_settings.assert_called_once_with(bypass_cache=True)
        assert snapshot == {"llm.provider": {"value": "ollama"}}

    def test_load_settings_returns_independent_snapshot_per_call(self):
        """Each ``_load_settings`` call returns the dict from the
        manager — independent calls don't share state by reference. This
        backs the snapshot semantics relied on by the research pipeline,
        where the snapshot is supposed to survive global setting changes
        that happen during the research run."""
        from src.local_deep_research.chat import routes as chat_routes

        snapshots: list[dict] = [
            {"llm.model": {"value": "first"}},
            {"llm.model": {"value": "second"}},
        ]
        results = []

        def _make_manager(*args, **kwargs):
            m = MagicMock()
            m.get_all_settings.return_value = snapshots[len(results)]
            return m

        with (
            patch.object(chat_routes, "get_user_db_session") as mock_get_db,
            patch.object(
                chat_routes,
                "SettingsManager",
                side_effect=_make_manager,
            ),
        ):
            mock_get_db.return_value.__enter__.return_value = MagicMock()
            results.append(chat_routes._load_settings("alice"))
            results.append(chat_routes._load_settings("alice"))

        # Two distinct dicts — modifying one doesn't bleed into the other.
        results[0]["llm.model"]["value"] = "mutated"
        assert results[1]["llm.model"]["value"] == "second"


class TestSettingsExtractionMatchesProduction:
    """The extraction pattern used by ``send_message`` to pull settings
    out of the snapshot before invoking the research pipeline. If the
    schema for one of these keys changes, this test fails before the
    routes silently fall back to a default the user didn't ask for."""

    EXTRACTION_KEYS = (
        "llm.provider",
        "llm.model",
        "search.tool",
        "search.iterations",
        "search.questions_per_iteration",
        "search.search_strategy",
    )

    def test_full_snapshot_round_trip(self):
        """Every key the chat route reads from the snapshot is present in
        the default settings shipped with the project
        (``defaults/default_settings.json`` loaded at import time by
        the SettingsManager). If any key is renamed or removed without
        updating the route, this fails."""
        import json
        from pathlib import Path

        import local_deep_research

        defaults_path = (
            Path(local_deep_research.__file__).parent
            / "defaults"
            / "default_settings.json"
        )
        with defaults_path.open() as fh:
            defaults = json.load(fh)

        for key in self.EXTRACTION_KEYS:
            assert key in defaults, (
                f"chat route reads {key!r} from settings_snapshot, but it "
                "is missing from default_settings.json — a rename will "
                "silently fall back to None at runtime."
            )
