"""
Suite de tests unitaires pour le système RAG (Indexer, Datastore, Retriever)

Adapté à votre implémentation spécifique avec:
- BaseDatastore (pas BaseDataStore)
- Datastore (pas DataStore)
- DataItem avec content/source (pas text/metadata)

Pour exécuter:
    pytest tests/test_rag.py -v
    
Pour exécuter avec couverture:
    pytest tests/test_rag.py --cov=src --cov-report=html
"""

import sys
from pathlib import Path

# Configuration du PYTHONPATH
project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
    sys.path.insert(0, str(project_root))

import pytest
import tempfile
import shutil
from typing import List

# Imports adaptés à votre structure
from src.interface.base_datastore import BaseDatastore, DataItem
from src.impl.datastore import Datastore

# Tentative d'import des autres composants (adapter selon vos fichiers)
try:
    from src.interface.base_indexer import BaseIndexer
    from src.impl.indexer import Indexer
    HAS_INDEXER = True
except ImportError:
    HAS_INDEXER = False
    print("⚠️ Indexer non trouvé - tests d'indexation désactivés")

try:
    from src.interface.base_retriever import BaseRetriever
    from src.impl.retriever import Retriever
    HAS_RETRIEVER = True
except ImportError:
    HAS_RETRIEVER = False
    print("⚠️ Retriever non trouvé - tests de récupération désactivés")


# ============================================================================
# FIXTURES - Configuration des tests
# ============================================================================

@pytest.fixture
def temp_dir():
    """Crée un répertoire temporaire pour les tests."""
    temp_path = tempfile.mkdtemp()
    yield temp_path
    shutil.rmtree(temp_path, ignore_errors=True)


@pytest.fixture
def sample_items():
    """Crée des DataItems de test."""
    return [
        DataItem(
            content="L'intelligence artificielle (IA) est un domaine de l'informatique.",
            source="doc1.pdf"
        ),
        DataItem(
            content="Python est un langage de programmation populaire pour l'IA.",
            source="doc2.pdf"
        ),
        DataItem(
            content="Les réseaux de neurones sont utilisés en deep learning.",
            source="doc3.pdf"
        ),
        DataItem(
            content="Le machine learning permet aux ordinateurs d'apprendre sans être explicitement programmés.",
            source="doc4.pdf"
        ),
    ]


@pytest.fixture
def datastore(temp_dir, monkeypatch):
    """Crée une instance du Datastore avec une base temporaire."""
    # Modifier le chemin de la DB pour les tests
    test_db_path = str(Path(temp_dir) / "test-lancedb")
    monkeypatch.setattr("src.impl.datastore.Datastore.DB_PATH", test_db_path)
    
    ds = Datastore()
    ds.reset_table()  # S'assurer que la table est vide
    yield ds
    
    # Cleanup
    try:
        ds.vector_db.drop_table(ds.DB_TABLE_NAME)
    except:
        pass


@pytest.fixture
def populated_datastore(datastore, sample_items):
    """Datastore pré-rempli avec des items."""
    datastore.add_items(sample_items)
    return datastore


@pytest.fixture
def indexer():
    """Crée une instance de l'Indexer si disponible."""
    if not HAS_INDEXER:
        pytest.skip("Indexer non disponible")
    return Indexer()


@pytest.fixture
def retriever(datastore):
    """Crée une instance du Retriever si disponible."""
    if not HAS_RETRIEVER:
        pytest.skip("Retriever non disponible")
    return Retriever(datastore=datastore)


# ============================================================================
# TESTS DATAITEM
# ============================================================================

class TestDataItem:
    """Tests pour la classe DataItem."""
    
    def test_dataitem_creation(self):
        """Test la création d'un DataItem."""
        item = DataItem(content="Test content", source="test.pdf")
        assert item.content == "Test content"
        assert item.source == "test.pdf"
    
    def test_dataitem_default_values(self):
        """Test les valeurs par défaut."""
        item = DataItem()
        assert item.content == ""
        assert item.source == ""
    
    def test_dataitem_validation(self):
        """Test la validation Pydantic."""
        # Pydantic devrait accepter ces types
        item = DataItem(content="text", source="source.pdf")
        assert isinstance(item.content, str)
        assert isinstance(item.source, str)


# ============================================================================
# TESTS DATASTORE
# ============================================================================

class TestDatastore:
    """Tests pour la classe Datastore."""
    
    def test_datastore_initialization(self, datastore):
        """Test l'initialisation du Datastore."""
        assert isinstance(datastore, BaseDatastore)
        assert datastore.vector_dimensions == 384
        assert datastore.model is not None
        assert datastore.table is not None
    
    def test_reset_table(self, datastore):
        """Test le reset de la table."""
        # Ajouter des items
        items = [DataItem(content="test", source="test.pdf")]
        datastore.add_items(items)
        
        # Reset
        table = datastore.reset_table()
        assert table is not None
        
        # Vérifier que la table est vide
        results = datastore.search_datastore("test", top_k=10)
        assert len(results) == 0
    
    def test_add_single_item(self, datastore):
        """Test l'ajout d'un seul item."""
        item = DataItem(
            content="Test de l'ajout d'un item unique",
            source="test_single.pdf"
        )
        datastore.add_items([item])
        
        # Vérifier que l'item peut être retrouvé
        results = datastore.search_datastore("test ajout", top_k=5)
        assert len(results) > 0
    
    def test_add_multiple_items(self, datastore, sample_items):
        """Test l'ajout de plusieurs items."""
        datastore.add_items(sample_items)
        
        # Vérifier que plusieurs items ont été ajoutés
        results = datastore.search_datastore("intelligence", top_k=10)
        assert len(results) > 0
    
    def test_add_empty_list(self, datastore):
        """Test l'ajout d'une liste vide."""
        # Ne devrait pas crasher
        datastore.add_items([])
        # Pas d'exception = succès
    
    def test_create_vector(self, datastore):
        """Test la création de vecteurs."""
        vector = datastore.create_vector("Test content")
        
        assert isinstance(vector, list)
        assert len(vector) == 384  # Dimension du modèle all-MiniLM-L6-v2
        assert all(isinstance(v, float) for v in vector)
    
    def test_create_vector_consistency(self, datastore):
        """Test que le même texte produit le même vecteur."""
        text = "Texte de test pour la cohérence"
        vector1 = datastore.create_vector(text)
        vector2 = datastore.create_vector(text)
        
        # Les vecteurs devraient être identiques (ou très proches)
        import numpy as np
        similarity = np.dot(vector1, vector2) / (np.linalg.norm(vector1) * np.linalg.norm(vector2))
        assert similarity > 0.99  # Très haute similarité
    
    def test_search_basic(self, populated_datastore):
        """Test une recherche basique."""
        results = populated_datastore.search_datastore("intelligence artificielle", top_k=3)
        
        assert len(results) > 0
        assert len(results) <= 3
        assert all(isinstance(r, str) for r in results)
    
    def test_search_relevance(self, populated_datastore):
        """Test la pertinence des résultats."""
        results = populated_datastore.search_datastore("Python programmation", top_k=5)
        
        assert len(results) > 0
        # Le premier résultat devrait contenir "Python"
        assert any("Python" in results[0] or "python" in results[0].lower() 
                  for _ in [results[0]])
    
    def test_search_top_k_limit(self, populated_datastore):
        """Test que top_k limite correctement les résultats."""
        results_2 = populated_datastore.search_datastore("test", top_k=2)
        results_4 = populated_datastore.search_datastore("test", top_k=4)
        
        assert len(results_2) <= 2
        assert len(results_4) <= 4
    
    def test_search_empty_query(self, populated_datastore):
        """Test avec une requête vide."""
        results = populated_datastore.search_datastore("", top_k=5)
        # Devrait retourner des résultats ou liste vide, pas d'erreur
        assert isinstance(results, list)
    
    def test_search_no_results(self, datastore):
        """Test une recherche sur une base vide."""
        results = datastore.search_datastore("query inexistante", top_k=5)
        assert results == []
    
    def test_search_special_characters(self, populated_datastore):
        """Test avec des caractères spéciaux."""
        results = populated_datastore.search_datastore("l'intelligence", top_k=5)
        assert isinstance(results, list)
    
    def test_multiple_searches_consistency(self, populated_datastore):
        """Test la cohérence sur plusieurs recherches."""
        query = "intelligence artificielle"
        results1 = populated_datastore.search_datastore(query, top_k=3)
        results2 = populated_datastore.search_datastore(query, top_k=3)
        
        # Les résultats devraient être identiques
        assert results1 == results2
    
    def test_large_batch_add(self, datastore):
        """Test l'ajout d'un grand nombre d'items."""
        large_batch = [
            DataItem(content=f"Document numéro {i} avec du contenu varié.", source=f"doc_{i}.pdf")
            for i in range(100)
        ]
        
        # Ne devrait pas crasher
        datastore.add_items(large_batch)
        
        # Vérifier que des résultats sont retournés
        results = datastore.search_datastore("document", top_k=10)
        assert len(results) > 0


# ============================================================================
# TESTS INDEXER (si disponible)
# ============================================================================

@pytest.mark.skipif(not HAS_INDEXER, reason="Indexer non disponible")
class TestIndexer:
    """Tests pour la classe Indexer."""
    
    def test_indexer_initialization(self, indexer):
        """Test l'initialisation de l'Indexer."""
        assert isinstance(indexer, BaseIndexer)
    
    def test_index_documents(self, indexer, temp_dir):
        """Test l'indexation de documents."""
        # Créer un document de test
        doc_path = Path(temp_dir) / "test_doc.pdf"
        doc_path.write_text("Contenu de test pour l'indexation.")
        
        items = indexer.index([str(doc_path)])
        
        assert len(items) > 0
        assert all(isinstance(item, DataItem) for item in items)
        assert all(hasattr(item, 'content') for item in items)
        assert all(hasattr(item, 'source') for item in items)


# ============================================================================
# TESTS RETRIEVER (si disponible)
# ============================================================================

@pytest.mark.skipif(not HAS_RETRIEVER, reason="Retriever non disponible")
class TestRetriever:
    """Tests pour la classe Retriever."""
    
    def test_retriever_initialization(self, retriever):
        """Test l'initialisation du Retriever."""
        assert isinstance(retriever, BaseRetriever)
        assert retriever.datastore is not None
    
    def test_retrieve_basic(self, retriever, populated_datastore):
        """Test une récupération basique."""
        retriever.datastore = populated_datastore
        results = retriever.search_retriever("intelligence artificielle", top_k=3)
        
        assert len(results) > 0
        assert len(results) <= 3


# ============================================================================
# TESTS D'INTÉGRATION
# ============================================================================

class TestIntegration:
    """Tests d'intégration du système complet."""
    
    @pytest.mark.skipif(not HAS_INDEXER, reason="Indexer requis")
    def test_full_pipeline(self, indexer, datastore, temp_dir):
        """Test du pipeline complet: indexation → stockage → recherche."""
        # 1. Créer un document
        doc_path = Path(temp_dir) / "integration_test.pdf"
        doc_path.write_text("""
        L'intelligence artificielle transforme le monde.
        Python est le langage privilégié pour l'IA.
        Les algorithmes de machine learning sont puissants.
        """)
        
        # 2. Indexation
        items = indexer.index([str(doc_path)])
        assert len(items) > 0
        
        # 3. Stockage
        datastore.add_items(items)
        
        # 4. Recherche
        results = datastore.search_datastore("intelligence artificielle Python", top_k=3)
        assert len(results) > 0
        
        # 5. Vérifier la pertinence
        top_result = results[0].lower()
        assert "intelligence" in top_result or "python" in top_result
    
    def test_incremental_addition(self, datastore, sample_items):
        """Test l'ajout incrémental de données."""
        # Ajouter en plusieurs fois
        datastore.add_items(sample_items[:2])
        results_1 = datastore.search_datastore("test", top_k=10)
        
        datastore.add_items(sample_items[2:])
        results_2 = datastore.search_datastore("test", top_k=10)
        
        # Le second devrait avoir plus de résultats potentiels
        assert len(results_2) >= len(results_1)


# ============================================================================
# TESTS DE ROBUSTESSE
# ============================================================================

class TestRobustness:
    """Tests de robustesse et gestion d'erreurs."""
    
    def test_unicode_content(self, datastore):
        """Test avec du contenu Unicode."""
        items = [
            DataItem(content="Émojis: 🎉 🎨 🚀", source="unicode.pdf"),
            DataItem(content="Caractères spéciaux: é à ç ñ", source="special.pdf"),
        ]
        
        datastore.add_items(items)
        results = datastore.search_datastore("émojis caractères", top_k=5)
        assert isinstance(results, list)
    
    def test_very_long_content(self, datastore):
        """Test avec du contenu très long."""
        long_content = "Test. " * 1000  # ~6000 caractères
        item = DataItem(content=long_content, source="long.pdf")
        
        # Ne devrait pas crasher
        datastore.add_items([item])
        results = datastore.search_datastore("test", top_k=1)
        assert len(results) >= 0
    
    def test_empty_content(self, datastore):
        """Test avec du contenu vide."""
        items = [DataItem(content="", source="empty.pdf")]
        
        # Ne devrait pas crasher
        datastore.add_items(items)


if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])