"""
Tests pour la gestion des erreurs du Pipeline.

Ce module teste le retry, le timeout et le mode résultats partiels.
"""

import pytest
from datetime import datetime, timedelta
from unittest.mock import Mock, patch

from pipeline_mco_pmsi.models.clinical import ClinicalDocument
from pipeline_mco_pmsi.models.metadata import StayMetadata
from pipeline_mco_pmsi.pipeline import Pipeline


def test_pipeline_retry_on_failure(db_session, rag_engine):
    """
    Test le retry avec exponential backoff en cas d'échec.
    
    Exigences: 14.1, 14.6
    """
    from pipeline_mco_pmsi.database.models import StayDB
    
    pipeline = Pipeline(
        db_session=db_session,
        rag_engine=rag_engine,
        max_retries=3,
        retry_delay=0.1,  # Court délai pour les tests
    )

    # Créer le séjour dans la base de données
    stay = StayDB(
        stay_id="stay_retry_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )
    db_session.add(stay)
    db_session.commit()

    documents = [
        ClinicalDocument(
            document_id="doc_retry_001",
            document_type="cr_medical",
            content="Diagnostic: Gastrite aiguë.",
            creation_date=datetime.now(),
            author="Dr. Test",
            priority=2,
        )
    ]

    stay_metadata = StayMetadata(
        stay_id="stay_retry_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )

    # Mock pour simuler des échecs puis succès
    call_count = 0
    original_extract = pipeline.clinical_facts_extractor.extract_facts

    def mock_extract_with_retry(structured_stay):
        nonlocal call_count
        call_count += 1
        if call_count < 2:  # Échoue la première fois
            raise RuntimeError("Erreur temporaire")
        return original_extract(structured_stay)

    with patch.object(
        pipeline.clinical_facts_extractor,
        "extract_facts",
        side_effect=mock_extract_with_retry,
    ):
        result = pipeline.process_stay(documents, stay_metadata)

    # Vérifications
    assert call_count == 2  # 1 échec + 1 succès
    assert result.success is True


def test_pipeline_timeout_with_partial_results(db_session, rag_engine):
    """
    Test le timeout avec mode résultats partiels activé.
    
    Exigences: 14.6
    """
    from pipeline_mco_pmsi.database.models import StayDB
    
    pipeline = Pipeline(
        db_session=db_session,
        rag_engine=rag_engine,
        timeout=0.01,  # Timeout très court pour forcer l'erreur
        partial_results_mode=True,
    )

    # Créer le séjour dans la base de données
    stay = StayDB(
        stay_id="stay_timeout_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )
    db_session.add(stay)
    db_session.commit()

    documents = [
        ClinicalDocument(
            document_id="doc_timeout_001",
            document_type="cr_medical",
            content="Diagnostic: Gastrite aiguë.",
            creation_date=datetime.now(),
            author="Dr. Test",
            priority=2,
        )
    ]

    stay_metadata = StayMetadata(
        stay_id="stay_timeout_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )

    # Mock pour ralentir le traitement
    import time

    def slow_extract(structured_stay):
        time.sleep(0.1)  # Plus long que le timeout
        return []

    with patch.object(
        pipeline.clinical_facts_extractor,
        "extract_facts",
        side_effect=slow_extract,
    ):
        result = pipeline.process_stay(documents, stay_metadata)

    # Vérifications
    assert result.success is False
    assert "timeout" in result.error_message.lower()
    
    # Vérifier qu'un problème de validation timeout a été ajouté
    timeout_issues = [
        issue for issue in result.validation_issues
        if "timeout" in issue.message.lower()
    ]
    assert len(timeout_issues) > 0


def test_pipeline_timeout_without_partial_results(db_session, rag_engine):
    """
    Test le timeout sans mode résultats partiels (doit lever une exception).
    
    Exigences: 14.6
    """
    from pipeline_mco_pmsi.database.models import StayDB
    
    pipeline = Pipeline(
        db_session=db_session,
        rag_engine=rag_engine,
        timeout=0.01,  # Timeout très court
        partial_results_mode=False,  # Mode désactivé
    )

    # Créer le séjour dans la base de données
    stay = StayDB(
        stay_id="stay_timeout_002",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )
    db_session.add(stay)
    db_session.commit()

    documents = [
        ClinicalDocument(
            document_id="doc_timeout_002",
            document_type="cr_medical",
            content="Diagnostic: Gastrite aiguë.",
            creation_date=datetime.now(),
            author="Dr. Test",
            priority=2,
        )
    ]

    stay_metadata = StayMetadata(
        stay_id="stay_timeout_002",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )

    # Mock pour ralentir le traitement
    import time

    def slow_extract(structured_stay):
        time.sleep(0.1)
        return []

    with patch.object(
        pipeline.clinical_facts_extractor,
        "extract_facts",
        side_effect=slow_extract,
    ):
        result = pipeline.process_stay(documents, stay_metadata)

    # Vérifications - devrait retourner un résultat d'erreur
    assert result.success is False
    assert result.error_message is not None


def test_pipeline_max_retries_exceeded(db_session, rag_engine):
    """
    Test que le pipeline échoue après avoir dépassé le nombre maximum de retries.
    
    Exigences: 14.1
    """
    from pipeline_mco_pmsi.database.models import StayDB
    
    pipeline = Pipeline(
        db_session=db_session,
        rag_engine=rag_engine,
        max_retries=2,
        retry_delay=0.1,
    )

    # Créer le séjour dans la base de données
    stay = StayDB(
        stay_id="stay_max_retry_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )
    db_session.add(stay)
    db_session.commit()

    documents = [
        ClinicalDocument(
            document_id="doc_max_retry_001",
            document_type="cr_medical",
            content="Diagnostic: Gastrite aiguë.",
            creation_date=datetime.now(),
            author="Dr. Test",
            priority=2,
        )
    ]

    stay_metadata = StayMetadata(
        stay_id="stay_max_retry_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )

    # Mock pour toujours échouer
    def always_fail(structured_stay):
        raise RuntimeError("Erreur persistante")

    with patch.object(
        pipeline.clinical_facts_extractor,
        "extract_facts",
        side_effect=always_fail,
    ):
        result = pipeline.process_stay(documents, stay_metadata)

    # Vérifications
    assert result.success is False
    assert result.error_message is not None
    assert "Erreur persistante" in result.error_message


def test_pipeline_error_handling_with_partial_data(db_session, rag_engine):
    """
    Test que les données partielles sont préservées en cas d'erreur.
    
    Exigences: 14.6
    """
    from pipeline_mco_pmsi.database.models import StayDB
    
    pipeline = Pipeline(
        db_session=db_session,
        rag_engine=rag_engine,
        partial_results_mode=True,
    )

    # Créer le séjour dans la base de données
    stay = StayDB(
        stay_id="stay_partial_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )
    db_session.add(stay)
    db_session.commit()

    documents = [
        ClinicalDocument(
            document_id="doc_partial_001",
            document_type="cr_medical",
            content="Diagnostic: Gastrite aiguë.",
            creation_date=datetime.now(),
            author="Dr. Test",
            priority=2,
        )
    ]

    stay_metadata = StayMetadata(
        stay_id="stay_partial_001",
        admission_date=datetime.now() - timedelta(days=1),
        discharge_date=datetime.now(),
        specialty="gastro-enterologie",
        unit="medecine",
        age=45,
        sex="M",
    )

    # Mock pour échouer après l'extraction des faits
    def fail_after_facts(proposal, facts, cim10_version, ccam_version):
        raise RuntimeError("Erreur lors de la vérification")

    with patch.object(
        pipeline.verificateur,
        "verify_proposal",
        side_effect=fail_after_facts,
    ):
        result = pipeline.process_stay(documents, stay_metadata)

    # Vérifications
    assert result.success is False
    # Les données partielles doivent être présentes
    assert result.structured_stay is not None
    # Le coding_proposal peut être None si l'erreur survient avant ou pendant le codage
    # On vérifie juste que le résultat est bien un échec avec un message d'erreur
    assert result.error_message is not None

