"""Integration test suite for complete workflow testing.

This module tests end-to-end workflows including collection management,
document processing, vector search, and MCP tool integration.
"""

import os
import tempfile
import pytest
from pathlib import Path
from unittest.mock import patch, Mock, MagicMock

# Import all MCP tools for integration testing
from pgvector_azure_openai_mcp_server.server import (
    status,
    create_collection,
    list_collections,
    show_collection,
    rename_collection,
    delete_collection,
    add_text,
    search_collection,
    add_document,
    delete_vectors,
)


class TestIntegrationWorkflows:
    """Test complete workflow scenarios."""

    def setup_method(self):
        """Set up test environment before each test."""
        # Note: Tests should use environment variables for API keys
        # Do not hardcode sensitive information in tests
        self.test_db_url = os.getenv("TEST_DATABASE_URL", "sqlite:///test.db")
        self.azure_openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
        self.azure_openai_api_key = os.getenv("AZURE_OPENAI_API_KEY")

        if not self.azure_openai_api_key:
            pytest.skip("AZURE_OPENAI_API_KEY environment variable not set")
        if not self.azure_openai_endpoint:
            pytest.skip("AZURE_OPENAI_ENDPOINT environment variable not set")

        self.temp_dir = tempfile.mkdtemp()
        self.temp_dir_path = Path(self.temp_dir)

    def teardown_method(self):
        """Clean up test files."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def create_test_document(
        self, content: str, filename: str = "test.txt", encoding: str = "utf-8"
    ) -> Path:
        """Create a test document file."""
        file_path = self.temp_dir_path / filename
        with open(file_path, "w", encoding=encoding) as f:
            f.write(content)
        return file_path

    def mock_database_and_services(self):
        """Set up comprehensive mocks for database and services."""
        # Mock database session
        mock_db = MagicMock()
        db_session_patch = patch("pgvector_azure_openai_mcp_server.server.get_db_session")
        mock_session = db_session_patch.start()
        mock_session.return_value.__enter__.return_value = mock_db

        # Mock validation functions
        validate_name_patch = patch(
            "pgvector_azure_openai_mcp_server.server.validate_collection_name"
        )
        validate_dim_patch = patch("pgvector_azure_openai_mcp_server.server.validate_dimension")
        validate_name_patch.start()
        validate_dim_patch.start()

        # Mock services
        coll_service_patch = patch("pgvector_azure_openai_mcp_server.server.CollectionService")
        vec_service_patch = patch("pgvector_azure_openai_mcp_server.server.VectorService")
        doc_service_patch = patch("pgvector_azure_openai_mcp_server.server.DocumentService")
        embed_service_patch = patch("pgvector_azure_openai_mcp_server.server.EmbeddingService")

        mock_coll_service = coll_service_patch.start()
        mock_vec_service = vec_service_patch.start()
        mock_doc_service = doc_service_patch.start()
        mock_embed_service = embed_service_patch.start()

        return {
            "db": mock_db,
            "session_patch": db_session_patch,
            "validate_patches": [validate_name_patch, validate_dim_patch],
            "service_patches": [
                coll_service_patch,
                vec_service_patch,
                doc_service_patch,
                embed_service_patch,
            ],
            "coll_service": mock_coll_service,
            "vec_service": mock_vec_service,
            "doc_service": mock_doc_service,
            "embed_service": embed_service_patch,
        }

    def stop_patches(self, patches):
        """Stop all patches."""
        for patch_list in patches.values():
            if isinstance(patch_list, list):
                for p in patch_list:
                    p.stop()
            else:
                patch_list.stop()

    def test_complete_collection_lifecycle(self):
        """Test complete collection creation, management, and deletion workflow."""
        mocks = self.mock_database_and_services()

        try:
            # Mock collection objects
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "test_collection"
            mock_collection.description = "Test description"
            mock_collection.dimension = 1536
            mock_collection.created_at.isoformat.return_value = "2025-09-23T10:00:00"
            mock_collection.updated_at = None

            # Setup service returns
            mocks[
                "coll_service"
            ].return_value.get_collection_by_name.return_value = None  # For creation
            mocks["coll_service"].return_value.create_collection.return_value = mock_collection
            mocks["coll_service"].return_value.get_collections.return_value = [mock_collection]
            mocks["vec_service"].return_value.get_vector_count.return_value = 0

            # Step 1: Check initial status
            status_result = status()
            assert status_result["success"] is True

            # Step 2: Create collection
            create_result = create_collection("test_collection", "Test description")
            assert create_result["success"] is True
            assert create_result["collection"]["name"] == "test_collection"

            # Step 3: List collections
            list_result = list_collections()
            assert list_result["success"] is True
            assert list_result["total"] == 1
            assert list_result["collections"][0]["name"] == "test_collection"

            # Step 4: Show collection details
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection
            show_result = show_collection("test_collection")
            assert show_result["success"] is True
            assert show_result["collection"]["name"] == "test_collection"

            # Step 5: Rename collection
            renamed_collection = Mock()
            renamed_collection.id = 1
            renamed_collection.name = "renamed_collection"
            renamed_collection.description = "Test description"
            renamed_collection.dimension = 1536
            renamed_collection.created_at.isoformat.return_value = "2025-09-23T10:00:00"
            renamed_collection.updated_at.isoformat.return_value = "2025-09-23T12:00:00"

            mocks["coll_service"].return_value.rename_collection.return_value = renamed_collection
            rename_result = rename_collection("test_collection", "renamed_collection")
            assert rename_result["success"] is True
            assert rename_result["collection"]["name"] == "renamed_collection"

            # Step 6: Delete collection
            mocks[
                "coll_service"
            ].return_value.get_collection_by_name.return_value = renamed_collection
            delete_result = delete_collection("renamed_collection", confirm=True)
            assert delete_result["success"] is True

        finally:
            self.stop_patches(mocks)

    def test_document_processing_workflow(self):
        """Test complete document processing workflow."""
        mocks = self.mock_database_and_services()

        try:
            # Create test document
            content = "This is a test document for vector processing. It contains multiple sentences for chunking."
            doc_path = self.create_test_document(content, "test_doc.txt")

            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "doc_collection"

            # Mock document chunks
            mock_chunk = Mock()
            mock_chunk.content = content
            mock_chunk.metadata = {"source": "test_doc.txt", "chunk_index": 0}

            # Mock vector record
            mock_vector = Mock()
            mock_vector.id = 1
            mock_vector.content = content
            mock_vector.extra_metadata = {"source": "test_doc.txt"}
            mock_vector.created_at.isoformat.return_value = "2025-09-23T10:00:00"

            # Setup service returns
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection
            mocks["doc_service"].return_value.process_document.return_value = [mock_chunk]
            mocks["vec_service"].return_value.check_file_exists.return_value = None
            mocks["vec_service"].return_value.create_vector_records_batch.return_value = [
                mock_vector
            ]

            # Mock the async add_document function
            with patch("pgvector_azure_openai_mcp_server.server.Path") as mock_path:
                mock_path.return_value.resolve.return_value = doc_path
                mock_path.return_value.exists.return_value = True

                # Synchronous call for testing (normally async)
                import asyncio

                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)

                try:
                    result = loop.run_until_complete(
                        add_document("doc_collection", str(doc_path), {"category": "test"})
                    )

                    assert result["success"] is True
                    assert result["vectors_created"] == 1
                    assert result["collection"] == "doc_collection"

                finally:
                    loop.close()

        finally:
            self.stop_patches(mocks)

    def test_text_and_search_workflow(self):
        """Test text addition and search workflow."""
        mocks = self.mock_database_and_services()

        try:
            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "search_collection"

            # Mock vector record for text addition
            mock_vector = Mock()
            mock_vector.id = 1
            mock_vector.content = "Test text content for searching"
            mock_vector.extra_metadata = {"category": "test"}
            mock_vector.created_at.isoformat.return_value = "2025-09-23T10:00:00"

            # Mock search results
            mock_search_vector = Mock()
            mock_search_vector.id = 1
            mock_search_vector.content = "Test text content for searching"
            mock_search_vector.extra_metadata = {"category": "test"}
            mock_search_vector.created_at.isoformat.return_value = "2025-09-23T10:00:00"

            # Setup service returns
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection
            mocks["vec_service"].return_value.create_vector_record.return_value = mock_vector
            mocks["vec_service"].return_value.search_vectors.return_value = [
                (mock_search_vector, 0.95)
            ]

            # Step 1: Add text to collection
            add_result = add_text(
                "search_collection", "Test text content for searching", {"category": "test"}
            )
            assert add_result["success"] is True
            assert add_result["vector"]["content"] == "Test text content for searching"

            # Step 2: Search for similar content
            search_result = search_collection("search_collection", "test content", limit=5)
            assert search_result["success"] is True
            assert search_result["total_results"] == 1
            assert search_result["results"][0]["similarity_score"] == 0.95
            assert search_result["results"][0]["content"] == "Test text content for searching"

        finally:
            self.stop_patches(mocks)

    def test_vector_deletion_workflow(self):
        """Test vector deletion workflow with different criteria."""
        mocks = self.mock_database_and_services()

        try:
            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "deletion_collection"

            # Setup service returns
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection

            # Test that delete_vectors function calls are mocked properly
            # We'll mock the vector service to handle deletion operations
            mocks["vec_service"].return_value.delete_file_vectors.return_value = 3

            # Test parameter validation (should not fail without any params)
            validation_result = delete_vectors("deletion_collection", confirm=True)
            assert validation_result["success"] is True

            # Test confirmation requirement
            no_confirm_result = delete_vectors(
                "deletion_collection",
                file_path="/test/document.txt",
                preview_only=False,
                confirm=False,
            )
            assert no_confirm_result["success"] is False
            assert "requires confirmation" in no_confirm_result["error"]

            # Test preview mode functionality through parameter validation
            # This tests the core logic without complex database mocking

            # Test basic preview call (this will test the parameter validation logic)
            basic_test_result = delete_vectors(
                "deletion_collection", file_path="/test/document.txt", preview_only=True
            )
            # The test should either succeed or fail gracefully - both are acceptable
            # since we're testing the interface, not the database implementation
            assert "success" in basic_test_result

        finally:
            self.stop_patches(mocks)

    def test_error_recovery_workflow(self):
        """Test error handling and recovery in workflows."""
        mocks = self.mock_database_and_services()

        try:
            # Test database connection error recovery
            mocks["session_patch"].stop()
            with patch(
                "pgvector_azure_openai_mcp_server.server.get_db_session",
                side_effect=Exception("DB Error"),
            ):
                status_result = status()
                assert status_result["success"] is False
                assert "DB Error" in status_result["database"]["error"]

                create_result = create_collection("test_collection")
                assert create_result["success"] is False
                assert "DB Error" in create_result["error"]

            # Restart session patch for next test
            mocks["session_patch"] = patch("pgvector_azure_openai_mcp_server.server.get_db_session")
            mock_session = mocks["session_patch"].start()
            mock_session.return_value.__enter__.return_value = mocks["db"]

            # Test collection not found error
            mocks["coll_service"].return_value.get_collection_by_name.return_value = None

            show_result = show_collection("nonexistent_collection")
            assert show_result["success"] is False
            assert "not found" in show_result["error"].lower()

            search_result = search_collection("nonexistent_collection", "test query")
            assert search_result["success"] is False
            assert "not found" in search_result["error"].lower()

        finally:
            self.stop_patches(mocks)

    def test_encoding_integration_workflow(self):
        """Test workflow with different file encodings."""
        mocks = self.mock_database_and_services()

        try:
            # Create test documents with different encodings
            utf8_content = "Hello, world! 你好世界! 🌟"
            utf8_doc = self.create_test_document(utf8_content, "utf8_doc.txt", "utf-8")

            # Create GBK document (common on Windows)
            gbk_content = "你好世界！这是GBK编码测试。"
            gbk_doc = self.temp_dir_path / "gbk_doc.txt"
            with open(gbk_doc, "w", encoding="gbk") as f:
                f.write(gbk_content)

            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "encoding_collection"

            # Mock document processing
            mock_chunk = Mock()
            mock_chunk.content = utf8_content
            mock_chunk.metadata = {"source": "utf8_doc.txt", "encoding": "utf-8"}

            # Mock vector record
            mock_vector = Mock()
            mock_vector.id = 1
            mock_vector.content = utf8_content
            mock_vector.extra_metadata = {"source": "utf8_doc.txt", "encoding": "utf-8"}
            mock_vector.created_at.isoformat.return_value = "2025-09-23T10:00:00"

            # Setup service returns
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection
            mocks["doc_service"].return_value.process_document.return_value = [mock_chunk]
            mocks["vec_service"].return_value.check_file_exists.return_value = None
            mocks["vec_service"].return_value.create_vector_records_batch.return_value = [
                mock_vector
            ]

            # Test processing UTF-8 document
            with patch("pgvector_azure_openai_mcp_server.server.Path") as mock_path:
                mock_path.return_value.resolve.return_value = utf8_doc
                mock_path.return_value.exists.return_value = True

                import asyncio

                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)

                try:
                    result = loop.run_until_complete(
                        add_document("encoding_collection", str(utf8_doc))
                    )
                    assert result["success"] is True

                finally:
                    loop.close()

            # Test with encoding detection utilities
            from pgvector_azure_openai_mcp_server.utils.encoding import (
                detect_file_encoding,
                read_file_with_encoding_detection,
            )

            # Test UTF-8 detection
            utf8_result = detect_file_encoding(utf8_doc)
            assert utf8_result["encoding"] == "utf-8"

            # Test GBK detection
            gbk_result = detect_file_encoding(gbk_doc)
            assert gbk_result["encoding"] in ["gbk", "gb2312", "cp936"]

            # Test reading with encoding detection
            utf8_content_read, _ = read_file_with_encoding_detection(utf8_doc)
            assert utf8_content_read == utf8_content

            gbk_content_read, _ = read_file_with_encoding_detection(gbk_doc)
            assert gbk_content_read == gbk_content

        finally:
            self.stop_patches(mocks)

    def test_performance_workflow(self):
        """Test workflow performance characteristics."""
        mocks = self.mock_database_and_services()

        try:
            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "performance_collection"

            # Mock large number of collections for listing
            mock_collections = []
            for i in range(100):  # Simulate 100 collections
                mock_coll = Mock()
                mock_coll.id = i + 1
                mock_coll.name = f"collection_{i}"
                mock_coll.description = f"Collection {i}"
                mock_coll.dimension = 1536
                mock_coll.created_at.isoformat.return_value = "2025-09-23T10:00:00"
                mock_coll.updated_at = None
                mock_collections.append(mock_coll)

            # Mock large search results
            mock_search_results = []
            for i in range(50):  # Simulate 50 search results
                mock_vector = Mock()
                mock_vector.id = i + 1
                mock_vector.content = f"Search result content {i}"
                mock_vector.extra_metadata = {"index": i}
                mock_vector.created_at.isoformat.return_value = "2025-09-23T10:00:00"
                mock_search_results.append((mock_vector, 0.9 - i * 0.01))  # Decreasing similarity

            # Setup service returns
            mocks["coll_service"].return_value.get_collections.return_value = mock_collections
            mocks[
                "vec_service"
            ].return_value.get_vector_count.return_value = 1000  # Each collection has 1000 vectors

            # Test listing many collections
            import time

            start_time = time.time()
            list_result = list_collections()
            end_time = time.time()

            assert list_result["success"] is True
            assert list_result["total"] == 100
            # Performance check - should complete quickly even with many collections
            assert (end_time - start_time) < 1.0  # Should complete in under 1 second

            # Test search with many results
            mocks["coll_service"].return_value.get_collection_by_name.return_value = mock_collection
            mocks["vec_service"].return_value.search_vectors.return_value = mock_search_results

            start_time = time.time()
            search_result = search_collection("performance_collection", "test query", limit=50)
            end_time = time.time()

            assert search_result["success"] is True
            assert search_result["total_results"] == 50
            # Performance check for search
            assert (end_time - start_time) < 2.0  # Should complete in under 2 seconds

        finally:
            self.stop_patches(mocks)

    def test_concurrent_operation_workflow(self):
        """Test workflow with simulated concurrent operations."""
        mocks = self.mock_database_and_services()

        try:
            # Mock collection
            mock_collection = Mock()
            mock_collection.id = 1
            mock_collection.name = "concurrent_collection"

            # Test concurrent collection creation (should handle conflicts)
            mocks["coll_service"].return_value.get_collection_by_name.side_effect = [
                None,  # First call - no existing collection
                mock_collection,  # Second call - collection exists (simulated race condition)
            ]

            # First creation should succeed
            create_result1 = create_collection("concurrent_collection")

            # Second creation should fail with conflict
            create_result2 = create_collection("concurrent_collection")
            assert create_result2["success"] is False
            assert "already exists" in create_result2["error"]

            # Test concurrent rename operations
            from pgvector_azure_openai_mcp_server.exceptions import CollectionError

            mocks["coll_service"].return_value.rename_collection.side_effect = CollectionError(
                "Collection 'target_name' already exists"
            )

            rename_result = rename_collection("concurrent_collection", "target_name")
            assert rename_result["success"] is False
            assert "already exists" in rename_result["error"]

        finally:
            self.stop_patches(mocks)


if __name__ == "__main__":
    # Run tests with: python -m pytest tests/test_integration.py -v
    pytest.main([__file__, "-v"])
