"""
Tests for LibraryRAGService.
"""

import pytest
from unittest.mock import Mock, MagicMock
from pathlib import Path


class TestLibraryRAGServiceInit:
    """Tests for LibraryRAGService initialization."""

    def test_init_with_default_parameters(self, mocker):
        """Initializes with default parameters."""
        # Mock database session
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock settings manager
        mock_settings_manager = Mock()
        mock_settings_manager.get_settings_snapshot.return_value = {}
        mocker.patch(
            "src.local_deep_research.settings.manager.SettingsManager",
            return_value=mock_settings_manager,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.LocalEmbeddingManager",
            return_value=mock_embedding_manager,
        )

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(username="test_user")

        assert service.username == "test_user"
        assert service.embedding_model == "all-MiniLM-L6-v2"
        assert service.embedding_provider == "sentence_transformers"
        assert service.chunk_size == 1000
        assert service.chunk_overlap == 200
        assert service.distance_metric == "cosine"
        assert service.normalize_vectors is True

    def test_init_with_custom_parameters(self, mocker):
        """Initializes with custom parameters."""
        # Mock database session
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock settings manager
        mock_settings_manager = Mock()
        mock_settings_manager.get_settings_snapshot.return_value = {}
        mocker.patch(
            "src.local_deep_research.settings.manager.SettingsManager",
            return_value=mock_settings_manager,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.LocalEmbeddingManager",
            return_value=mock_embedding_manager,
        )

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_model="custom-model",
            chunk_size=500,
            chunk_overlap=100,
            distance_metric="l2",
        )

        assert service.embedding_model == "custom-model"
        assert service.chunk_size == 500
        assert service.chunk_overlap == 100
        assert service.distance_metric == "l2"

    def test_init_with_provided_embedding_manager(self, mocker):
        """Uses provided embedding manager instead of creating new one."""
        # Mock database session
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        # Provided embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        assert service.embedding_manager == mock_embedding_manager


class TestLibraryRAGServiceIndexHash:
    """Tests for index hash generation."""

    def test_get_index_hash_deterministic(self, mocker):
        """Index hash is deterministic for same inputs."""
        # Create service with minimal mocking
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        hash1 = service._get_index_hash("collection_123", "model-a", "type-b")
        hash2 = service._get_index_hash("collection_123", "model-a", "type-b")

        assert hash1 == hash2

    def test_get_index_hash_different_for_different_inputs(self, mocker):
        """Index hash differs for different inputs."""
        # Create service with minimal mocking
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        hash1 = service._get_index_hash("collection_123", "model-a", "type-b")
        hash2 = service._get_index_hash("collection_456", "model-a", "type-b")
        hash3 = service._get_index_hash("collection_123", "model-x", "type-b")

        assert hash1 != hash2
        assert hash1 != hash3
        assert hash2 != hash3


class TestLibraryRAGServiceIndexPath:
    """Tests for index path generation."""

    def test_get_index_path_returns_path(self, mocker):
        """Returns valid Path for index."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        path = service._get_index_path("abc123hash")

        assert isinstance(path, Path)
        assert path.suffix == ".faiss"
        assert "abc123hash" in str(path)


class TestLibraryRAGServiceIndexDocument:
    """Tests for document indexing."""

    def test_index_document_not_found(self, mocker):
        """Returns error when document not found."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        result = service.index_document("nonexistent-doc", "collection-123")

        assert result["status"] == "error"
        assert "not found" in result["error"].lower()

    def test_index_document_no_text_content(self, mocker):
        """Returns error when document has no text content."""
        # Mock document with no text
        mock_doc = Mock()
        mock_doc.id = "doc-123"
        mock_doc.text_content = None

        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc
        mock_session.query.return_value.filter_by.return_value.all.return_value = []

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        result = service.index_document("doc-123", "collection-123")

        assert result["status"] == "error"
        assert "no text content" in result["error"].lower()

    def test_index_document_already_indexed_skips(self, mocker):
        """Skips indexing when document already indexed."""
        # Mock document
        mock_doc = Mock()
        mock_doc.id = "doc-123"
        mock_doc.text_content = "Some text content"

        # Mock document collection (already indexed)
        mock_doc_collection = Mock()
        mock_doc_collection.indexed = True
        mock_doc_collection.chunk_count = 5

        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_doc
        mock_session.query.return_value.filter_by.return_value.all.return_value = [
            mock_doc_collection
        ]

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        result = service.index_document(
            "doc-123", "collection-123", force_reindex=False
        )

        assert result["status"] == "skipped"
        assert result["chunk_count"] == 5


class TestLibraryRAGServiceGetRAGStats:
    """Tests for RAG statistics."""

    def test_get_rag_stats_returns_dict(self, mocker):
        """Returns dictionary with RAG statistics."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        # Mock query results
        mock_session.query.return_value.filter_by.return_value.count.return_value = 10
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock get_default_library_id
        mocker.patch(
            "src.local_deep_research.database.library_init.get_default_library_id",
            return_value="default-lib-id",
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        stats = service.get_rag_stats("collection-123")

        assert isinstance(stats, dict)
        assert "total_documents" in stats
        assert "indexed_documents" in stats
        assert "unindexed_documents" in stats
        assert "total_chunks" in stats
        assert "chunk_size" in stats
        assert "chunk_overlap" in stats


class TestLibraryRAGServiceRemoveDocument:
    """Tests for removing document from RAG."""

    def test_remove_document_not_in_collection(self, mocker):
        """Returns error when document not in collection."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()
        mock_embedding_manager._delete_chunks_from_db = Mock(return_value=0)

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        result = service.remove_document_from_rag("doc-123", "collection-123")

        assert result["status"] == "error"
        assert "not found" in result["error"].lower()


class TestLibraryRAGServiceSearchLibrary:
    """Tests for library search."""

    def test_search_library_not_implemented(self, mocker):
        """Raises NotImplementedError for search."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        with pytest.raises(NotImplementedError):
            service.search_library("test query")


class TestLibraryRAGServiceLoadOrCreateFaissIndex:
    """Tests for FAISS index loading/creation."""

    def test_load_or_create_faiss_index_creates_new(self, mocker):
        """Creates new FAISS index when none exists."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager with dimension
        mock_embedding_manager = Mock()
        mock_embeddings = Mock()
        mock_embeddings.embed_query.return_value = [
            0.1
        ] * 384  # 384-dim embedding
        mock_embedding_manager.embeddings = mock_embeddings

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mock_integrity.verify_file.return_value = (False, "File not found")
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        # Mock RAGIndex creation
        mock_rag_index = Mock()
        mock_rag_index.index_path = "/tmp/test.faiss"
        mock_rag_index.embedding_dimension = 384
        mock_rag_index.id = "rag-idx-123"

        # Mock FAISS
        mock_faiss = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FAISS",
            return_value=mock_faiss,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        # Mock _get_or_create_rag_index
        service._get_or_create_rag_index = Mock(return_value=mock_rag_index)

        # Mock Path.exists to return False (no existing index)
        mocker.patch("pathlib.Path.exists", return_value=False)

        result = service.load_or_create_faiss_index("collection-123")

        assert result is not None


class TestLibraryRAGServiceIndexBatch:
    """Tests for batch document indexing."""

    def test_index_documents_batch_returns_dict(self, mocker):
        """Returns dictionary with results per document."""
        mock_session = MagicMock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)

        # Mock documents
        mock_doc = Mock()
        mock_doc.id = "doc-123"
        mock_doc.text_content = "Some content"
        mock_doc.title = "Test Doc"

        mock_session.query.return_value.filter.return_value.all.return_value = [
            mock_doc
        ]
        mock_session.query.return_value.filter.return_value.first.return_value = None

        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_user_db_session",
            return_value=mock_session,
        )

        # Mock embedding manager
        mock_embedding_manager = Mock()
        mock_embedding_manager.embeddings = Mock()

        # Mock text splitter
        mock_splitter = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.get_text_splitter",
            return_value=mock_splitter,
        )

        # Mock integrity manager
        mock_integrity = Mock()
        mocker.patch(
            "src.local_deep_research.research_library.services.library_rag_service.FileIntegrityManager",
            return_value=mock_integrity,
        )

        from src.local_deep_research.research_library.services.library_rag_service import (
            LibraryRAGService,
        )

        service = LibraryRAGService(
            username="test_user",
            embedding_manager=mock_embedding_manager,
        )

        # Mock index_document to return success
        service.index_document = Mock(
            return_value={"status": "success", "chunk_count": 5}
        )

        result = service.index_documents_batch(
            [("doc-123", "Test Doc")], "collection-123"
        )

        assert isinstance(result, dict)
        assert "doc-123" in result
