"""
Comprehensive tests for research_library/routes/rag_routes.py

Tests cover:
- get_rag_service function
- RAG service initialization
- Executor management
"""

import pytest
from unittest.mock import Mock, patch, MagicMock


class TestGetAutoIndexExecutor:
    """Tests for _get_auto_index_executor function."""

    def test_executor_creation(self):
        """Test that executor is created lazily."""
        from local_deep_research.research_library.routes import rag_routes

        # Reset global state
        rag_routes._auto_index_executor = None

        executor = rag_routes._get_auto_index_executor()

        assert executor is not None
        assert rag_routes._auto_index_executor is not None

        # Cleanup
        rag_routes._shutdown_auto_index_executor()

    def test_executor_reused(self):
        """Test that executor is reused on subsequent calls."""
        from local_deep_research.research_library.routes import rag_routes

        # Reset global state
        rag_routes._auto_index_executor = None

        executor1 = rag_routes._get_auto_index_executor()
        executor2 = rag_routes._get_auto_index_executor()

        assert executor1 is executor2

        # Cleanup
        rag_routes._shutdown_auto_index_executor()


class TestShutdownAutoIndexExecutor:
    """Tests for _shutdown_auto_index_executor function."""

    def test_shutdown_clears_executor(self):
        """Test that shutdown clears the executor."""
        from local_deep_research.research_library.routes import rag_routes

        # Create executor first
        _ = rag_routes._get_auto_index_executor()
        assert rag_routes._auto_index_executor is not None

        # Shutdown
        rag_routes._shutdown_auto_index_executor()

        assert rag_routes._auto_index_executor is None

    def test_shutdown_handles_none(self):
        """Test that shutdown handles None executor."""
        from local_deep_research.research_library.routes import rag_routes

        rag_routes._auto_index_executor = None

        # Should not raise
        rag_routes._shutdown_auto_index_executor()


class TestGetRagService:
    """Tests for get_rag_service function."""

    @pytest.fixture
    def mock_settings(self):
        """Create mock settings manager."""
        mock = Mock()
        mock.get_setting.side_effect = lambda key, default=None: {
            "local_search_embedding_model": "test-model",
            "local_search_embedding_provider": "sentence_transformers",
            "local_search_chunk_size": "1000",
            "local_search_chunk_overlap": "200",
            "local_search_splitter_type": "recursive",
            "local_search_text_separators": '["\n\n", "\n", ". ", " ", ""]',
            "local_search_distance_metric": "cosine",
            "local_search_normalize_vectors": True,
            "local_search_index_type": "flat",
        }.get(key, default)
        return mock

    def test_get_rag_service_no_collection(self, mock_settings):
        """Test getting RAG service without collection."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                ) as mock_rag:
                    mock_service = Mock()
                    mock_rag.return_value = mock_service

                    service = get_rag_service()

                    assert service == mock_service
                    mock_rag.assert_called_once()

    def test_get_rag_service_with_collection_existing_settings(
        self, mock_settings
    ):
        """Test getting RAG service with collection that has existing settings."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_collection = Mock()
        mock_collection.embedding_model = "existing-model"
        mock_collection.embedding_model_type = Mock()
        mock_collection.embedding_model_type.value = "existing_provider"
        mock_collection.chunk_size = 500
        mock_collection.chunk_overlap = 100
        mock_collection.splitter_type = "simple"
        mock_collection.text_separators = ["\n"]
        mock_collection.distance_metric = "euclidean"
        mock_collection.normalize_vectors = False
        mock_collection.index_type = "hnsw"

        mock_db_session = MagicMock()
        mock_query = MagicMock()
        mock_db_session.query.return_value = mock_query
        mock_query.filter_by.return_value = mock_query
        mock_query.first.return_value = mock_collection

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.database.session_context.get_user_db_session"
                ) as mock_ctx:
                    mock_ctx.return_value.__enter__ = Mock(
                        return_value=mock_db_session
                    )
                    mock_ctx.return_value.__exit__ = Mock(return_value=False)

                    with patch(
                        "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                    ) as mock_rag:
                        mock_service = Mock()
                        mock_rag.return_value = mock_service

                        service = get_rag_service(collection_id="col123")

                        assert service == mock_service
                        # Should use collection's settings
                        call_kwargs = mock_rag.call_args[1]
                        assert (
                            call_kwargs["embedding_model"] == "existing-model"
                        )

    def test_get_rag_service_with_new_collection(self, mock_settings):
        """Test getting RAG service with new collection (no stored settings)."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_collection = Mock()
        mock_collection.embedding_model = None  # No existing settings

        mock_db_session = MagicMock()
        mock_query = MagicMock()
        mock_db_session.query.return_value = mock_query
        mock_query.filter_by.return_value = mock_query
        mock_query.first.return_value = mock_collection

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.database.session_context.get_user_db_session"
                ) as mock_ctx:
                    mock_ctx.return_value.__enter__ = Mock(
                        return_value=mock_db_session
                    )
                    mock_ctx.return_value.__exit__ = Mock(return_value=False)

                    with patch(
                        "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                    ) as mock_rag:
                        mock_service = Mock()
                        mock_rag.return_value = mock_service

                        service = get_rag_service(collection_id="new_col")

                        assert service == mock_service
                        # Should use default settings
                        call_kwargs = mock_rag.call_args[1]
                        assert call_kwargs["embedding_model"] == "test-model"

    def test_json_text_separators_parsing(self, mock_settings):
        """Test that JSON text separators are properly parsed."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                ) as mock_rag:
                    mock_service = Mock()
                    mock_rag.return_value = mock_service

                    get_rag_service()

                    # Check that text_separators was parsed
                    call_kwargs = mock_rag.call_args[1]
                    assert isinstance(call_kwargs["text_separators"], list)

    def test_invalid_json_text_separators(self):
        """Test handling of invalid JSON text separators."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_settings = Mock()
        mock_settings.get_setting.side_effect = lambda key, default=None: {
            "local_search_embedding_model": "test-model",
            "local_search_embedding_provider": "sentence_transformers",
            "local_search_chunk_size": "1000",
            "local_search_chunk_overlap": "200",
            "local_search_splitter_type": "recursive",
            "local_search_text_separators": "invalid json",  # Invalid!
            "local_search_distance_metric": "cosine",
            "local_search_normalize_vectors": True,
            "local_search_index_type": "flat",
        }.get(key, default)

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                ) as mock_rag:
                    mock_service = Mock()
                    mock_rag.return_value = mock_service

                    # Should not raise, should use default
                    get_rag_service()

                    call_kwargs = mock_rag.call_args[1]
                    # Should have fallen back to default
                    assert isinstance(call_kwargs["text_separators"], list)


class TestRagBlueprintImport:
    """Tests for RAG blueprint import."""

    def test_blueprint_exists(self):
        """Test that RAG blueprint exists."""
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        assert rag_bp is not None
        assert rag_bp.name == "rag"
        assert rag_bp.url_prefix == "/library"


class TestNormalizeVectorsHandling:
    """Tests for normalize_vectors string/bool handling."""

    def test_normalize_vectors_string_true(self):
        """Test normalize_vectors string 'true' is converted to bool."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_settings = Mock()
        mock_settings.get_setting.side_effect = lambda key, default=None: {
            "local_search_embedding_model": "test-model",
            "local_search_embedding_provider": "sentence_transformers",
            "local_search_chunk_size": "1000",
            "local_search_chunk_overlap": "200",
            "local_search_splitter_type": "recursive",
            "local_search_text_separators": "[]",
            "local_search_distance_metric": "cosine",
            "local_search_normalize_vectors": "true",  # String!
            "local_search_index_type": "flat",
        }.get(key, default)

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                ) as mock_rag:
                    mock_service = Mock()
                    mock_rag.return_value = mock_service

                    get_rag_service()

                    call_kwargs = mock_rag.call_args[1]
                    assert call_kwargs["normalize_vectors"] is True

    def test_normalize_vectors_string_false(self):
        """Test normalize_vectors string 'false' is converted to bool."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_settings = Mock()
        mock_settings.get_setting.side_effect = lambda key, default=None: {
            "local_search_embedding_model": "test-model",
            "local_search_embedding_provider": "sentence_transformers",
            "local_search_chunk_size": "1000",
            "local_search_chunk_overlap": "200",
            "local_search_splitter_type": "recursive",
            "local_search_text_separators": "[]",
            "local_search_distance_metric": "cosine",
            "local_search_normalize_vectors": "false",  # String!
            "local_search_index_type": "flat",
        }.get(key, default)

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                ) as mock_rag:
                    mock_service = Mock()
                    mock_rag.return_value = mock_service

                    get_rag_service()

                    call_kwargs = mock_rag.call_args[1]
                    assert call_kwargs["normalize_vectors"] is False


class TestRagApiRoutes:
    """Tests for RAG API routes."""

    def test_get_current_settings_route(self):
        """Test /api/rag/settings GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/settings")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_test_embedding_route(self):
        """Test /api/rag/test-embedding POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/test-embedding",
                json={"text": "test text"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_available_models_route(self):
        """Test /api/rag/models GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/models")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_index_info_route(self):
        """Test /api/rag/info GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/info")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_rag_stats_route(self):
        """Test /api/rag/stats GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/stats")
            assert response.status_code in [200, 302, 401, 403, 500]


class TestRagIndexRoutes:
    """Tests for RAG indexing routes."""

    def test_index_document_route(self):
        """Test /api/rag/index-document POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/index-document",
                json={"document_id": "doc123"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_remove_document_route(self):
        """Test /api/rag/remove-document POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/remove-document",
                json={"document_id": "doc123"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_index_research_route(self):
        """Test /api/rag/index-research POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/index-research",
                json={"research_id": "research123"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_index_all_route(self):
        """Test /api/rag/index-all GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/index-all")
            assert response.status_code in [200, 302, 401, 403, 500]


class TestRagCollectionRoutes:
    """Tests for RAG collection routes."""

    def test_get_collections_route(self):
        """Test /api/collections GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/collections")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_create_collection_route(self):
        """Test /api/collections POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={"name": "Test Collection"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_update_collection_route(self):
        """Test /api/collections/<id> PUT endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.put(
                "/library/api/collections/collection123",
                json={"name": "Updated Collection"},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 401, 403, 404, 500]

    def test_delete_collection_route(self):
        """Test /api/collections/<id> DELETE endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.delete("/library/api/collections/collection123")
            assert response.status_code in [200, 302, 401, 403, 404, 500]


class TestRagPageRoutes:
    """Tests for RAG page routes."""

    def test_embedding_settings_page_route(self):
        """Test /embedding-settings page route exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/embedding-settings")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_collections_page_route(self):
        """Test /collections page route exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/collections")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_collection_details_page_route(self):
        """Test /collections/<id> page route exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/collections/collection123")
            assert response.status_code in [200, 302, 401, 403, 404, 500]

    def test_collection_create_page_route(self):
        """Test /collections/create page route exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/collections/create")
            assert response.status_code in [200, 302, 401, 403, 500]


class TestRagBackgroundIndexRoutes:
    """Tests for RAG background indexing routes."""

    def test_start_background_index_route(self):
        """Test /api/collections/<id>/index/background POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections/collection123/index/background"
            )
            assert response.status_code in [200, 302, 401, 403, 404, 500]

    def test_get_index_status_route(self):
        """Test /api/collections/<id>/index/status GET endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/collections/collection123/index/status"
            )
            assert response.status_code in [200, 302, 401, 403, 404, 500]

    def test_cancel_indexing_route(self):
        """Test /api/collections/<id>/index/cancel POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections/collection123/index/cancel"
            )
            assert response.status_code in [200, 302, 401, 403, 404, 500]


class TestRagUploadRoutes:
    """Tests for RAG upload routes."""

    def test_upload_to_collection_route(self):
        """Test /api/collections/<id>/upload POST endpoint exists."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            # Test without file (will likely fail but route should exist)
            response = client.post(
                "/library/api/collections/collection123/upload"
            )
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]


class TestExtractTextFromFile:
    """Tests for extract_text_from_file function."""

    def test_extract_text_from_txt_file(self):
        """Test extracting text from .txt file."""
        from local_deep_research.research_library.routes.rag_routes import (
            extract_text_from_file,
        )
        import io

        content = b"Hello, this is a test text file."
        file_obj = io.BytesIO(content)

        text = extract_text_from_file(file_obj, "test.txt")
        assert "Hello" in text

    def test_extract_text_from_md_file(self):
        """Test extracting text from .md file."""
        from local_deep_research.research_library.routes.rag_routes import (
            extract_text_from_file,
        )
        import io

        content = b"# Header\n\nThis is markdown content."
        file_obj = io.BytesIO(content)

        text = extract_text_from_file(file_obj, "test.md")
        assert "Header" in text or "markdown" in text

    def test_extract_text_from_unknown_file(self):
        """Test extracting text from unknown file type."""
        from local_deep_research.research_library.routes.rag_routes import (
            extract_text_from_file,
        )
        import io

        content = b"Some content"
        file_obj = io.BytesIO(content)

        text = extract_text_from_file(file_obj, "test.xyz")
        # Should return something or empty string
        assert text is not None or text == ""


# ============= Extended Tests for Phase 3.2 Coverage =============


class TestConfigureRagEndpoint:
    """Extended tests for RAG configuration endpoint."""

    def test_configure_rag_missing_embedding_model(self):
        """Test configure RAG with missing embedding_model."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_provider": "sentence_transformers",
                    "chunk_size": 1000,
                    "chunk_overlap": 200,
                },
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_configure_rag_missing_provider(self):
        """Test configure RAG with missing embedding_provider."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_model": "all-MiniLM-L6-v2",
                    "chunk_size": 1000,
                    "chunk_overlap": 200,
                },
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_configure_rag_with_all_advanced_settings(self):
        """Test configure RAG with all advanced settings."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_model": "all-MiniLM-L6-v2",
                    "embedding_provider": "sentence_transformers",
                    "chunk_size": 500,
                    "chunk_overlap": 100,
                    "splitter_type": "sentence",
                    "text_separators": ["\n\n", "\n", ". "],
                    "distance_metric": "euclidean",
                    "normalize_vectors": False,
                    "index_type": "hnsw",
                    "collection_id": "test_collection",
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]


class TestIndexDocumentEndpoint:
    """Extended tests for index document endpoint."""

    def test_index_document_missing_text_doc_id(self):
        """Test index document without text_doc_id."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/index-document",
                json={},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_index_document_with_force_reindex(self):
        """Test index document with force_reindex flag."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/index-document",
                json={
                    "text_doc_id": "doc123",
                    "force_reindex": True,
                    "collection_id": "coll123",
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]


class TestRemoveDocumentEndpoint:
    """Extended tests for remove document endpoint."""

    def test_remove_document_missing_text_doc_id(self):
        """Test remove document without text_doc_id."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/remove-document",
                json={},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]


class TestIndexResearchEndpoint:
    """Extended tests for index research endpoint."""

    def test_index_research_missing_research_id(self):
        """Test index research without research_id."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/index-research",
                json={},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]


class TestIndexLocalEndpoint:
    """Extended tests for index local library endpoint."""

    def test_index_local_missing_path(self):
        """Test index local without path."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/index-local")
            assert response.status_code in [302, 400, 401, 403]

    def test_index_local_path_traversal_attempt(self):
        """Test index local with path traversal attempt."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/rag/index-local?path=../../etc/passwd"
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_index_local_with_patterns(self):
        """Test index local with custom patterns."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/rag/index-local?path=/tmp&patterns=*.pdf,*.txt"
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]


class TestGetDocumentsEndpoint:
    """Extended tests for get documents endpoint."""

    def test_get_documents_with_pagination(self):
        """Test get documents with pagination."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/rag/documents?page=2&per_page=25"
            )
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_documents_filter_indexed(self):
        """Test get documents with indexed filter."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/documents?filter=indexed")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_documents_filter_unindexed(self):
        """Test get documents with unindexed filter."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/documents?filter=unindexed")
            assert response.status_code in [200, 302, 401, 403, 500]

    def test_get_documents_with_collection_id(self):
        """Test get documents with collection_id."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/rag/documents?collection_id=coll123"
            )
            assert response.status_code in [200, 302, 401, 403, 500]


class TestCollectionEndpoints:
    """Extended tests for collection management endpoints."""

    def test_create_collection_missing_name(self):
        """Test create collection without name."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_create_collection_with_all_fields(self):
        """Test create collection with all optional fields."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={
                    "name": "Test Collection",
                    "description": "A test collection",
                    "collection_type": "research",
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 201, 302, 400, 401, 403, 500]

    def test_get_single_collection(self):
        """Test get single collection."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/collections/coll123")
            assert response.status_code in [200, 302, 401, 403, 404, 500]


class TestCollectionDocumentEndpoints:
    """Extended tests for collection document management."""

    def test_add_document_to_collection(self):
        """Test adding document to collection."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections/coll123/documents",
                json={"document_id": "doc123"},
                content_type="application/json",
            )
            assert response.status_code in [
                200,
                201,
                302,
                400,
                401,
                403,
                404,
                500,
            ]

    def test_remove_document_from_collection(self):
        """Test removing document from collection."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.delete(
                "/library/api/collections/coll123/documents/doc123"
            )
            assert response.status_code in [200, 302, 401, 403, 404, 405, 500]

    def test_get_collection_documents(self):
        """Test getting documents in a collection."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/collections/coll123/documents")
            assert response.status_code in [200, 302, 401, 403, 404, 500]


class TestSearchEndpoint:
    """Extended tests for search endpoint."""

    def test_search_collection_missing_query(self):
        """Test search without query."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections/coll123/search",
                json={},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403, 404]

    def test_search_collection_with_limit(self):
        """Test search with limit parameter."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections/coll123/search",
                json={"query": "test query", "limit": 5},
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]


class TestFileUploadEndpoint:
    """Extended tests for file upload endpoint."""

    def test_upload_pdf_file(self):
        """Test uploading a PDF file."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )
        from io import BytesIO

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            data = {"file": (BytesIO(b"%PDF-1.4 fake content"), "test.pdf")}
            response = client.post(
                "/library/api/collections/coll123/upload",
                data=data,
                content_type="multipart/form-data",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]

    def test_upload_txt_file(self):
        """Test uploading a text file."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )
        from io import BytesIO

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            data = {"file": (BytesIO(b"Test text content"), "test.txt")}
            response = client.post(
                "/library/api/collections/coll123/upload",
                data=data,
                content_type="multipart/form-data",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]


class TestTestEmbeddingEndpoint:
    """Extended tests for test embedding endpoint."""

    def test_test_embedding_missing_provider(self):
        """Test embedding test without provider."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/test-embedding",
                json={"model": "all-MiniLM-L6-v2"},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_test_embedding_missing_model(self):
        """Test embedding test without model."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/test-embedding",
                json={"provider": "sentence_transformers"},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]


class TestRagEdgeCases:
    """Extended edge case tests for RAG routes."""

    def test_very_large_chunk_size(self):
        """Test configuration with very large chunk size."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_model": "model",
                    "embedding_provider": "provider",
                    "chunk_size": 999999999,
                    "chunk_overlap": 200,
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]

    def test_negative_chunk_size(self):
        """Test configuration with negative chunk size."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_model": "model",
                    "embedding_provider": "provider",
                    "chunk_size": -100,
                    "chunk_overlap": 200,
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]

    def test_overlap_larger_than_chunk(self):
        """Test configuration where overlap > chunk size."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/rag/configure",
                json={
                    "embedding_model": "model",
                    "embedding_provider": "provider",
                    "chunk_size": 100,
                    "chunk_overlap": 500,
                },
                content_type="application/json",
            )
            assert response.status_code in [200, 302, 400, 401, 403, 500]

    def test_sql_injection_in_collection_id(self):
        """Test SQL injection attempt in collection ID."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get(
                "/library/api/collections/'; DROP TABLE collections; --"
            )
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]

    def test_special_chars_in_collection_name(self):
        """Test creating collection with special characters."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={"name": "<script>alert('xss')</script>"},
                content_type="application/json",
            )
            assert response.status_code in [200, 201, 302, 400, 401, 403, 500]

    def test_unicode_in_collection_name(self):
        """Test creating collection with unicode characters."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={"name": "测试集合 コレクション مجموعة"},
                content_type="application/json",
            )
            assert response.status_code in [200, 201, 302, 400, 401, 403, 500]

    def test_empty_collection_name(self):
        """Test creating collection with empty name."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={"name": ""},
                content_type="application/json",
            )
            assert response.status_code in [302, 400, 401, 403]

    def test_very_long_collection_name(self):
        """Test creating collection with very long name."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post(
                "/library/api/collections",
                json={"name": "a" * 10000},
                content_type="application/json",
            )
            assert response.status_code in [200, 201, 302, 400, 401, 403, 500]


class TestCollectionNormalizeVectors:
    """Tests for collection normalize_vectors handling."""

    def test_collection_normalize_vectors_string_handling(self):
        """Test that collection normalize_vectors handles string values."""
        from local_deep_research.research_library.routes.rag_routes import (
            get_rag_service,
        )

        mock_settings = Mock()
        mock_settings.get_setting.side_effect = lambda key, default=None: {
            "local_search_embedding_model": "test-model",
            "local_search_embedding_provider": "sentence_transformers",
            "local_search_chunk_size": "1000",
            "local_search_chunk_overlap": "200",
            "local_search_splitter_type": "recursive",
            "local_search_text_separators": "[]",
            "local_search_distance_metric": "cosine",
            "local_search_normalize_vectors": True,
            "local_search_index_type": "flat",
        }.get(key, default)
        mock_settings.get_bool_setting.return_value = True

        mock_collection = Mock()
        mock_collection.embedding_model = "coll-model"
        mock_collection.embedding_model_type = Mock()
        mock_collection.embedding_model_type.value = "sentence_transformers"
        mock_collection.chunk_size = 500
        mock_collection.chunk_overlap = 100
        mock_collection.splitter_type = "recursive"
        mock_collection.text_separators = ["\n"]
        mock_collection.distance_metric = "cosine"
        mock_collection.normalize_vectors = "true"  # String value
        mock_collection.index_type = "flat"

        mock_db_session = MagicMock()
        mock_query = MagicMock()
        mock_db_session.query.return_value = mock_query
        mock_query.filter_by.return_value = mock_query
        mock_query.first.return_value = mock_collection

        with patch(
            "local_deep_research.research_library.routes.rag_routes.get_settings_manager",
            return_value=mock_settings,
        ):
            with patch(
                "local_deep_research.research_library.routes.rag_routes.session",
                {"username": "testuser"},
            ):
                with patch(
                    "local_deep_research.database.session_context.get_user_db_session"
                ) as mock_ctx:
                    mock_ctx.return_value.__enter__ = Mock(
                        return_value=mock_db_session
                    )
                    mock_ctx.return_value.__exit__ = Mock(return_value=False)

                    with patch(
                        "local_deep_research.research_library.routes.rag_routes.LibraryRAGService"
                    ) as mock_rag:
                        mock_service = Mock()
                        mock_rag.return_value = mock_service

                        get_rag_service(collection_id="col123")

                        call_kwargs = mock_rag.call_args[1]
                        # String "true" should be converted to boolean True
                        assert call_kwargs["normalize_vectors"] is True


class TestIndexAllStreamingResponse:
    """Tests for index-all SSE streaming response."""

    def test_index_all_returns_sse_response(self):
        """Test that index-all returns SSE response."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.get("/library/api/rag/index-all")
            # Should return 200 with text/event-stream or require auth
            assert response.status_code in [200, 302, 401, 403, 500]
            if response.status_code == 200:
                assert "text/event-stream" in response.content_type


class TestAutoIndexTrigger:
    """Tests for auto-index trigger endpoint."""

    def test_trigger_auto_index(self):
        """Test triggering auto-index."""
        from flask import Flask
        from local_deep_research.research_library.routes.rag_routes import (
            rag_bp,
        )

        app = Flask(__name__)
        app.config["SECRET_KEY"] = "test-secret"
        app.register_blueprint(rag_bp)

        with app.test_client() as client:
            response = client.post("/library/api/rag/trigger-auto-index")
            assert response.status_code in [200, 302, 400, 401, 403, 404, 500]
