"""
Tests for MCard RAG System

Tests for embedding providers, vector store, and RAG engine.
"""


import pytest

from mcard import MCard

# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests: Configuration
# ─────────────────────────────────────────────────────────────────────────────


class TestRAGConfig:
    """Tests for RAG configuration."""

    def test_default_config(self):
        """Test default configuration values."""
        from mcard.rag.config import DEFAULT_EMBEDDING_MODEL, RAGConfig

        config = RAGConfig()
        assert config.embedding_model == DEFAULT_EMBEDDING_MODEL
        assert config.embedding_provider == "ollama"
        assert config.top_k == 5
        assert config.dimensions == 768  # nomic-embed-text

    def test_custom_config(self):
        """Test custom configuration."""
        from mcard.rag.config import RAGConfig

        config = RAGConfig(embedding_model="bge-m3", top_k=10, chunk_size=500)
        assert config.embedding_model == "bge-m3"
        assert config.dimensions == 1024
        assert config.top_k == 10
        assert config.chunk_size == 500

    def test_invalid_model_raises(self):
        """Test that invalid model raises ValueError."""
        from mcard.rag.config import RAGConfig

        with pytest.raises(ValueError, match="Unknown embedding model"):
            RAGConfig(embedding_model="invalid-model")

    def test_invalid_top_k_raises(self):
        """Test that invalid top_k raises ValueError."""
        from mcard.rag.config import RAGConfig

        with pytest.raises(ValueError, match="top_k must be"):
            RAGConfig(top_k=0)


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests: Embedding Provider
# ─────────────────────────────────────────────────────────────────────────────


class TestOllamaEmbeddingProvider:
    """Tests for Ollama embedding provider."""

    def test_initialization(self):
        """Test provider initialization."""
        from mcard.rag.embeddings import OllamaEmbeddingProvider

        provider = OllamaEmbeddingProvider()
        assert provider.model_name == "nomic-embed-text"
        assert provider.dimensions == 768
        assert provider.provider_name == "ollama"

    def test_custom_model(self):
        """Test provider with custom model."""
        from mcard.rag.embeddings import OllamaEmbeddingProvider

        provider = OllamaEmbeddingProvider(model="bge-m3")
        assert provider.model_name == "bge-m3"
        assert provider.dimensions == 1024

    def test_custom_base_url(self):
        """Test provider with custom base URL."""
        from mcard.rag.embeddings import OllamaEmbeddingProvider

        provider = OllamaEmbeddingProvider(base_url="http://custom:11434")
        assert provider.base_url == "http://custom:11434"


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests: Vector Store
# ─────────────────────────────────────────────────────────────────────────────


class TestVectorStore:
    """Tests for MCard vector store."""

    def test_initialization(self):
        """Test vector store initialization."""
        from mcard.rag.vector import MCardVectorStore

        # Use in-memory database
        store = MCardVectorStore(db_path=":memory:")
        assert store.db_path == ":memory:"
        assert store.conn is not None

    def test_vector_serialization(self):
        """Test vector serialization and deserialization."""
        from mcard.rag.vector.store import deserialize_vector, serialize_vector

        vector = [0.1, 0.2, 0.3, 0.4, 0.5]
        blob = serialize_vector(vector)
        restored = deserialize_vector(blob)

        for a, b in zip(vector, restored):
            assert abs(a - b) < 0.0001

    def test_cosine_similarity(self):
        """Test cosine similarity calculation."""
        from mcard.rag.vector.store import cosine_similarity

        # Identical vectors
        a = [1.0, 0.0, 0.0]
        b = [1.0, 0.0, 0.0]
        assert cosine_similarity(a, b) == pytest.approx(1.0)

        # Orthogonal vectors
        a = [1.0, 0.0, 0.0]
        b = [0.0, 1.0, 0.0]
        assert cosine_similarity(a, b) == pytest.approx(0.0)

        # Opposite vectors
        a = [1.0, 0.0, 0.0]
        b = [-1.0, 0.0, 0.0]
        assert cosine_similarity(a, b) == pytest.approx(-1.0)

    def test_chunking(self):
        """Test text chunking."""
        from mcard.rag.config import RAGConfig
        from mcard.rag.vector import MCardVectorStore

        config = RAGConfig(chunk_size=100, chunk_overlap=20)
        store = MCardVectorStore(db_path=":memory:", config=config)

        # Short text should not be chunked
        short_text = "This is a short text."
        chunks = store._chunk_text(short_text)
        assert len(chunks) == 1

        # Long text should be chunked
        long_text = "A" * 250
        chunks = store._chunk_text(long_text)
        assert len(chunks) > 1


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests: Search Results
# ─────────────────────────────────────────────────────────────────────────────


class TestVectorSearchResult:
    """Tests for VectorSearchResult."""

    def test_result_creation(self):
        """Test search result creation."""
        from mcard.rag.vector import VectorSearchResult

        result = VectorSearchResult(
            hash="abc123", score=0.95, chunk_index=0, chunk_text="Sample text"
        )

        assert result.hash == "abc123"
        assert result.score == 0.95
        assert result.chunk_index == 0
        assert result.chunk_text == "Sample text"

    def test_result_repr(self):
        """Test search result string representation."""
        from mcard.rag.vector import VectorSearchResult

        result = VectorSearchResult(hash="abc123def456", score=0.9523)
        assert "abc123de" in repr(result)
        assert "0.9523" in repr(result)


# ─────────────────────────────────────────────────────────────────────────────
# Integration Tests (Require Ollama)
# ─────────────────────────────────────────────────────────────────────────────


@pytest.mark.skipif(
    True,  # Will be enabled when Ollama is available
    reason="Live tests require Ollama to be running with embedding models",
)
class TestLiveEmbeddings:
    """Live integration tests for embeddings."""

    def test_embed_single(self):
        """Test embedding a single text."""
        from mcard.rag.embeddings import OllamaEmbeddingProvider

        provider = OllamaEmbeddingProvider()
        embedding = provider.embed("Hello world")

        assert len(embedding) == 768
        assert all(isinstance(x, float) for x in embedding)

    def test_embed_batch(self):
        """Test batch embedding."""
        from mcard.rag.embeddings import OllamaEmbeddingProvider

        provider = OllamaEmbeddingProvider()
        texts = ["Hello", "World", "Test"]
        embeddings = provider.embed_batch(texts)

        assert len(embeddings) == 3
        for emb in embeddings:
            assert len(emb) == 768


@pytest.mark.skipif(
    True,  # Will be enabled when Ollama is available
    reason="Live tests require Ollama to be running",
)
class TestLiveRAG:
    """Live integration tests for RAG system."""

    def test_index_and_search(self):
        """Test indexing and searching MCards."""
        from mcard.rag import MCardRAGEngine

        rag = MCardRAGEngine(vector_db_path=":memory:")

        # Create and index MCards
        mcard1 = MCard("Python is a programming language.")
        mcard2 = MCard("JavaScript runs in browsers.")

        rag.vector_store.index(mcard1)
        rag.vector_store.index(mcard2)

        # Search
        results = rag.search("What is Python?")
        assert len(results) > 0
        assert results[0].hash == mcard1.hash

    def test_rag_query(self):
        """Test RAG query with answer generation."""
        from mcard.rag import MCardRAGEngine

        rag = MCardRAGEngine(vector_db_path=":memory:")

        mcard = MCard("MCard is a content-addressable storage library.")
        rag.vector_store.index(mcard)

        response = rag.query("What is MCard?")
        assert response.answer
        assert mcard.hash in response.sources


# ─────────────────────────────────────────────────────────────────────────────
# Unit Tests: PersistentIndexer
# ─────────────────────────────────────────────────────────────────────────────


class TestPersistentIndexer:
    """Tests for PersistentIndexer."""

    def test_singleton_pattern(self):
        """Test that PersistentIndexer is a singleton."""
        from mcard.rag import PersistentIndexer

        # Reset first
        PersistentIndexer.reset_instance()

        indexer1 = PersistentIndexer()
        indexer2 = PersistentIndexer()

        assert indexer1 is indexer2

        PersistentIndexer.reset_instance()

    def test_get_indexer_convenience(self):
        """Test get_indexer convenience function."""
        from mcard.rag import PersistentIndexer, get_indexer

        PersistentIndexer.reset_instance()

        indexer = get_indexer()
        assert indexer is not None
        assert hasattr(indexer, "search")
        assert hasattr(indexer, "index_mcard")

        PersistentIndexer.reset_instance()

    def test_indexer_stats(self):
        """Test indexer statistics."""
        from mcard.rag import PersistentIndexer

        PersistentIndexer.reset_instance()
        indexer = PersistentIndexer()

        stats = indexer.get_stats()

        assert "vector_db_path" in stats
        assert "embedding_model" in stats
        assert "indexed_count" in stats
        assert "vector_count" in stats

        PersistentIndexer.reset_instance()


class TestConvenienceFunctions:
    """Tests for convenience functions."""

    def test_semantic_search_function(self):
        """Test semantic_search convenience function exists."""
        from mcard.rag import semantic_search

        # Just verify it's callable
        assert callable(semantic_search)

    def test_index_mcard_function(self):
        """Test index_mcard convenience function exists."""
        from mcard.rag import index_mcard

        assert callable(index_mcard)
