"""
Tests pour le mapping des termes cliniques vers les codes via les index alphabétiques.

Exigences: 27.3, 27.4
"""

import pytest
from pathlib import Path
from pipeline_mco_pmsi.rag.rag_engine import RAGEngine
from pipeline_mco_pmsi.rag.referentiels_manager import ReferentielsManager, Chunk


@pytest.fixture
def rag_engine_with_alpha_index(tmp_path):
    """Crée un RAGEngine avec des index alphabétiques de test."""
    # Créer des chunks d'index alphabétique simulés
    alpha_chunks_cim10 = [
        Chunk(
            chunk_id="cim10_alpha_2026_0",
            referentiel_type="cim10",
            referentiel_version="2026",
            content="""Gastrite - voir K29.7
Gastrite aiguë - K29.0
Gastrite chronique - K29.5
Gastro-entérite - A09""",
            metadata={"chunk_type": "alphabetical_index", "letter": "G", "codes": "K29.7,K29.0,K29.5,A09"},
            chunk_index=0
        ),
        Chunk(
            chunk_id="cim10_alpha_2026_1",
            referentiel_type="cim10",
            referentiel_version="2026",
            content="""Appendicite - K35
Appendicite aiguë - K35.8
Appendicite chronique - K36""",
            metadata={"chunk_type": "alphabetical_index", "letter": "A", "codes": "K35,K35.8,K36"},
            chunk_index=1
        )
    ]
    
    # Créer des chunks de codes analytiques simulés
    code_chunks_cim10 = [
        Chunk(
            chunk_id="cim10_2026_0",
            referentiel_type="cim10",
            referentiel_version="2026",
            content="""K29.0 Gastrite aiguë hémorragique
Gastrite aiguë avec hémorragie
Exclut: érosion gastrique (K25.-)""",
            metadata={"chunk_type": "code", "code": "K29.0"},
            chunk_index=0
        ),
        Chunk(
            chunk_id="cim10_2026_1",
            referentiel_type="cim10",
            referentiel_version="2026",
            content="""K29.7 Gastrite, sans précision
Gastrite SAI""",
            metadata={"chunk_type": "code", "code": "K29.7"},
            chunk_index=1
        )
    ]
    
    # Sauvegarder les chunks dans le bon répertoire (data/ et non data/referentiels/)
    data_dir = tmp_path / "data"
    data_dir.mkdir(parents=True, exist_ok=True)
    
    import json
    all_chunks = alpha_chunks_cim10 + code_chunks_cim10
    chunks_data = [
        {
            "chunk_id": c.chunk_id,
            "referentiel_type": c.referentiel_type,
            "referentiel_version": c.referentiel_version,
            "content": c.content,
            "metadata": c.metadata,
            "chunk_index": c.chunk_index
        }
        for c in all_chunks
    ]
    
    # Utiliser le bon nom de fichier dans data/
    chunks_file = data_dir / "cim10_2026_chunks.json"
    with open(chunks_file, "w", encoding="utf-8") as f:
        json.dump(chunks_data, f, ensure_ascii=False, indent=2)
    
    # Créer un index FAISS vide (pour les tests)
    import faiss
    import numpy as np
    dimension = 768
    index = faiss.IndexFlatL2(dimension)
    
    # Ajouter des vecteurs aléatoires pour chaque chunk
    vectors = np.random.rand(len(all_chunks), dimension).astype('float32')
    index.add(vectors)
    
    index_file = data_dir / "cim10_2026_index.faiss"
    faiss.write_index(index, str(index_file))
    
    # Créer le ReferentielsManager et RAGEngine
    ref_manager = ReferentielsManager(data_dir=str(tmp_path / "data"))
    engine = RAGEngine(data_dir=str(tmp_path / "data"), referentiels_manager=ref_manager)
    return engine


def test_map_clinical_term_to_codes_from_alpha_index(rag_engine_with_alpha_index):
    """
    Test que le mapping d'un terme clinique vers des codes fonctionne
    en utilisant les index alphabétiques.
    
    Exigence: 27.3
    """
    # Mapper "Gastrite" vers des codes
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="Gastrite",
        referentiel_type="cim10",
        version="2026",
        top_k=3
    )
    
    # Vérifier qu'on a des résultats
    assert len(candidates) > 0, "Aucun code trouvé pour 'Gastrite'"
    
    # Vérifier que les codes sont pertinents
    codes = [c.code for c in candidates]
    assert any(code.startswith("K29") for code in codes), "Les codes K29.x (gastrite) devraient être trouvés"
    
    # Vérifier que les résultats de l'index alphabétique ont un boost
    alpha_results = [c for c in candidates if c.source == "alphabetical_index"]
    if alpha_results:
        assert alpha_results[0].similarity_score > 0, "Le score devrait être positif"


def test_map_clinical_term_handles_synonyms(rag_engine_with_alpha_index):
    """
    Test que le mapping gère les synonymes et variations.
    
    Exigence: 27.4
    """
    # Mapper "Gastrite aiguë" (variation de "Gastrite")
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="Gastrite aiguë",
        referentiel_type="cim10",
        version="2026",
        top_k=3
    )
    
    # Vérifier qu'on trouve le code spécifique
    codes = [c.code for c in candidates]
    assert "K29.0" in codes or any(code.startswith("K29") for code in codes), \
        "Le code K29.0 (gastrite aiguë) devrait être trouvé"


def test_map_clinical_term_returns_top_k(rag_engine_with_alpha_index):
    """
    Test que le mapping retourne au maximum top_k résultats.
    
    Exigence: 27.3
    """
    top_k = 2
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="Gastrite",
        referentiel_type="cim10",
        version="2026",
        top_k=top_k
    )
    
    # Vérifier qu'on ne dépasse pas top_k
    assert len(candidates) <= top_k, f"Le nombre de résultats devrait être ≤ {top_k}"


def test_map_clinical_term_deduplicates_codes(rag_engine_with_alpha_index):
    """
    Test que le mapping déduplique les codes trouvés dans plusieurs sources.
    
    Exigence: 27.3
    """
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="Gastrite",
        referentiel_type="cim10",
        version="2026",
        top_k=10
    )
    
    # Vérifier qu'il n'y a pas de doublons
    codes = [c.code for c in candidates]
    assert len(codes) == len(set(codes)), "Les codes ne devraient pas être dupliqués"


def test_get_synonyms_and_variations(rag_engine_with_alpha_index):
    """
    Test que la récupération de synonymes fonctionne.
    
    Exigence: 27.4
    """
    synonyms = rag_engine_with_alpha_index.get_synonyms_and_variations(
        term="Gastrite",
        referentiel_type="cim10",
        version="2026"
    )
    
    # Vérifier qu'on a des synonymes
    # Note: Peut être vide si les chunks de test ne contiennent pas de synonymes
    assert isinstance(synonyms, list), "Le résultat devrait être une liste"


def test_map_clinical_term_with_no_results(rag_engine_with_alpha_index):
    """
    Test que le mapping gère correctement l'absence de résultats.
    
    Exigence: 27.3
    """
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="TermeInexistantXYZ123",
        referentiel_type="cim10",
        version="2026",
        top_k=5
    )
    
    # Vérifier qu'on retourne une liste vide ou des résultats peu pertinents
    assert isinstance(candidates, list), "Le résultat devrait être une liste"
    # Les scores devraient être faibles si des résultats sont retournés
    if candidates:
        assert all(c.similarity_score < 0.5 for c in candidates), \
            "Les scores devraient être faibles pour un terme inexistant"


def test_alphabetical_index_priority_in_reranking(rag_engine_with_alpha_index):
    """
    Test que les résultats de l'index alphabétique sont priorisés dans le reranking.
    
    Exigence: 27.6 (via Propriété 57)
    """
    candidates = rag_engine_with_alpha_index.map_clinical_term_to_codes(
        clinical_term="Gastrite",
        referentiel_type="cim10",
        version="2026",
        top_k=5
    )
    
    # Séparer les résultats par source
    alpha_results = [c for c in candidates if c.source == "alphabetical_index"]
    code_results = [c for c in candidates if c.source == "analytical_code"]
    
    # Si on a des résultats des deux sources, vérifier la priorisation
    if alpha_results and code_results:
        # Les résultats de l'index alphabétique devraient avoir des scores plus élevés
        max_alpha_score = max(c.similarity_score for c in alpha_results)
        max_code_score = max(c.similarity_score for c in code_results)
        
        assert max_alpha_score >= max_code_score, \
            "Les résultats de l'index alphabétique devraient avoir des scores plus élevés"
