"""
Tests for LibraryRAGSearchEngine (search_engine_library.py, 0% coverage).

Covers:
- __init__: initialization with/without username
- search: no username, no collections, exception handling
- _get_previews: delegates to search
- _get_full_content: snippet-only mode, no username, no doc_id, success path
- close: no-op
"""

from unittest.mock import MagicMock, Mock, patch


MODULE = "local_deep_research.web_search_engines.engines.search_engine_library"


def _make_engine(username="testuser", settings_snapshot=None):
    """Create a LibraryRAGSearchEngine with mocked dependencies."""
    from local_deep_research.web_search_engines.engines.search_engine_library import (
        LibraryRAGSearchEngine,
    )

    snapshot = settings_snapshot or {"_username": username}
    engine = LibraryRAGSearchEngine(
        llm=MagicMock(),
        max_results=10,
        settings_snapshot=snapshot,
    )
    return engine


class TestLibraryRAGSearchEngineInit:
    def test_init_with_username(self):
        engine = _make_engine(username="testuser")
        assert engine.username == "testuser"
        assert engine.is_local is True

    def test_init_without_username(self):
        engine = _make_engine(settings_snapshot={"_username": None})
        assert engine.username is None

    def test_init_reads_embedding_settings(self):
        engine = _make_engine(username="user1")
        # Defaults should be set
        assert engine.embedding_model is not None
        assert engine.chunk_size is not None


class TestSearch:
    def test_search_no_username_returns_empty(self):
        engine = _make_engine(settings_snapshot={})
        result = engine.search("test query")
        assert result == []

    def test_search_no_collections_returns_empty(self):
        engine = _make_engine()
        mock_service = MagicMock()
        mock_service.get_all_collections.return_value = []
        with patch(f"{MODULE}.LibraryService", return_value=mock_service):
            result = engine.search("test query")
        assert result == []

    def test_search_exception_returns_empty(self):
        engine = _make_engine()
        with patch(
            f"{MODULE}.LibraryService", side_effect=RuntimeError("fail")
        ):
            result = engine.search("test query")
        assert result == []

    def test_search_collection_no_rag_index_skips(self):
        engine = _make_engine()
        mock_service = MagicMock()
        mock_service.get_all_collections.return_value = [
            {"id": "col1", "name": "Test Collection"}
        ]

        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        # RAGIndex query returns None
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        with patch(f"{MODULE}.LibraryService", return_value=mock_service):
            with patch(
                f"{MODULE}.get_user_db_session", return_value=mock_session
            ):
                result = engine.search("test query")

        assert result == []

    def test_search_collection_exception_continues(self):
        """Exception in one collection doesn't stop search of others."""
        engine = _make_engine()
        mock_service = MagicMock()
        mock_service.get_all_collections.return_value = [
            {"id": "col1", "name": "Collection 1"},
        ]

        with patch(f"{MODULE}.LibraryService", return_value=mock_service):
            with patch(
                f"{MODULE}.get_user_db_session",
                side_effect=RuntimeError("db error"),
            ):
                result = engine.search("test query")

        assert result == []

    def test_search_no_results_across_collections(self):
        """When all collections have no indexed docs, returns empty."""
        engine = _make_engine()
        mock_service = MagicMock()
        mock_service.get_all_collections.return_value = [
            {"id": "col1", "name": "Empty Collection"}
        ]

        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mock_rag_index = MagicMock()
        mock_rag_index.embedding_model = "all-MiniLM-L6-v2"
        mock_rag_index.embedding_model_type = MagicMock(
            value="sentence_transformers"
        )
        mock_rag_index.chunk_size = 1000
        mock_rag_index.chunk_overlap = 200
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_rag_index

        mock_rag_service = MagicMock()
        mock_rag_service.__enter__ = Mock(return_value=mock_rag_service)
        mock_rag_service.__exit__ = Mock(return_value=False)
        mock_rag_service.get_rag_stats.return_value = {"indexed_documents": 0}

        with patch(f"{MODULE}.LibraryService", return_value=mock_service):
            with patch(
                f"{MODULE}.get_user_db_session", return_value=mock_session
            ):
                with patch(
                    f"{MODULE}.LibraryRAGService", return_value=mock_rag_service
                ):
                    result = engine.search("test query")

        assert result == []


class TestGetPreviews:
    def test_delegates_to_search(self):
        engine = _make_engine()
        with patch.object(
            engine, "search", return_value=[{"title": "test"}]
        ) as mock_search:
            result = engine._get_previews("test query", limit=5)
        mock_search.assert_called_once_with("test query", 5, None, None)
        assert result == [{"title": "test"}]


class TestGetFullContent:
    def _patch_search_config(self, snippet_only=False):
        """Patch the search_config module that gets imported inside _get_full_content."""
        mock_config = MagicMock()
        mock_config.SEARCH_SNIPPETS_ONLY = snippet_only
        return patch.dict(
            "sys.modules",
            {"local_deep_research.config.search_config": mock_config},
        )

    def test_snippet_only_mode_returns_items(self):
        """When SEARCH_SNIPPETS_ONLY is True, returns items unchanged."""
        engine = _make_engine()
        items = [{"title": "test", "snippet": "content"}]

        from local_deep_research.config import search_config

        original = getattr(search_config, "SEARCH_SNIPPETS_ONLY", None)
        search_config.SEARCH_SNIPPETS_ONLY = True
        try:
            result = engine._get_full_content(items)
        finally:
            if original is None:
                if hasattr(search_config, "SEARCH_SNIPPETS_ONLY"):
                    delattr(search_config, "SEARCH_SNIPPETS_ONLY")
            else:
                search_config.SEARCH_SNIPPETS_ONLY = original

        assert result == items

    def test_no_username_returns_items(self):
        engine = _make_engine(settings_snapshot={"_username": None})
        items = [{"title": "test"}]
        result = engine._get_full_content(items)
        assert result == items

    def test_exception_returns_items(self):
        engine = _make_engine()
        items = [{"title": "test", "metadata": {"document_id": "doc1"}}]

        with patch(
            f"{MODULE}.get_user_db_session",
            side_effect=RuntimeError("fail"),
        ):
            result = engine._get_full_content(items)

        assert result == items


class TestClose:
    def test_close_is_noop(self):
        engine = _make_engine()
        engine.close()  # Should not raise
