"""Tests for the run-snapshot username injection in AdvancedSearchSystem.

The LangGraph agent re-instantiates the search engine *per tool call* from the
run's ``settings_snapshot``; registering a user's document collections needs
the username, which collection registration reads only from
``settings_snapshot["_username"]``. ``AdvancedSearchSystem.__init__`` injects it
— it is the single consumer of every strategy-running path (web run,
programmatic API, benchmarks) — so a collection/library primary works for all
of them. Regression: a collection primary previously failed inside the agent
with "Unknown search engine 'collection_…'".
"""

from unittest.mock import Mock, patch

from local_deep_research.search_system import (
    AdvancedSearchSystem,
    _ensure_snapshot_username,
)


class TestEnsureSnapshotUsername:
    """Unit contract of the injection helper."""

    def test_injects_username_when_missing(self):
        snap = {"search.tool": "collection_abc"}
        result = _ensure_snapshot_username(snap, "alice")
        assert result["_username"] == "alice"
        # search.tool (and everything else) is preserved.
        assert result["search.tool"] == "collection_abc"

    def test_does_not_mutate_caller_dict(self):
        snap = {"search.tool": "collection_abc"}
        result = _ensure_snapshot_username(snap, "alice")
        assert "_username" not in snap  # original untouched
        assert result is not snap

    def test_preserves_existing_username(self):
        snap = {"_username": "already", "search.tool": "x"}
        result = _ensure_snapshot_username(snap, "alice")
        # An explicit value is never overwritten, and no copy is made.
        assert result is snap
        assert result["_username"] == "already"

    def test_noop_when_username_none(self):
        snap = {"search.tool": "x"}
        result = _ensure_snapshot_username(snap, None)
        assert result is snap
        assert "_username" not in result

    def test_noop_when_username_empty_string(self):
        snap = {"search.tool": "x"}
        result = _ensure_snapshot_username(snap, "")
        assert result is snap
        assert "_username" not in result

    def test_noop_when_snapshot_not_dict(self):
        # Defensive: a non-dict snapshot is returned unchanged, never crashes.
        assert _ensure_snapshot_username(None, "alice") is None


class TestAdvancedSearchSystemInjectsUsername:
    """Locks the call site: constructing the system with a username must put it
    into the snapshot the strategy (and its per-call engine creation) reads.
    ``create_strategy`` is patched so the assertion is about the injection, not
    strategy construction details.
    """

    def test_init_injects_username_into_snapshot(self):
        with patch(
            "local_deep_research.search_system_factory.create_strategy",
            return_value=Mock(),
        ):
            system = AdvancedSearchSystem(
                llm=Mock(),
                search=Mock(),
                settings_snapshot={"search.tool": "collection_abc"},
                username="alice",
            )
        assert system.settings_snapshot["_username"] == "alice"
        # The configured primary is preserved alongside the injected username.
        assert system.settings_snapshot["search.tool"] == "collection_abc"

    def test_init_without_username_leaves_snapshot_clean(self):
        with patch(
            "local_deep_research.search_system_factory.create_strategy",
            return_value=Mock(),
        ):
            system = AdvancedSearchSystem(
                llm=Mock(),
                search=Mock(),
                settings_snapshot={"search.tool": "searxng"},
            )
        assert "_username" not in system.settings_snapshot
