"""
Tests d'intégration pour le système de règles configurables.

Ce module teste l'intégration du RulesManager avec le PMSIValidator
et le Pipeline pour appliquer des règles spécifiques à l'établissement.

Exigences: 20.4
"""

import pytest
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock

from pipeline_mco_pmsi.models.clinical import (
    ClinicalDocument,
    ClinicalFact,
    Evidence,
    Qualifier,
    Span,
    StructuredStay,
)
from pipeline_mco_pmsi.models.coding import Code, CodingProposal
from pipeline_mco_pmsi.models.metadata import ModelVersion, StayMetadata
from pipeline_mco_pmsi.rag.rag_engine import RAGEngine
from pipeline_mco_pmsi.rules.rules_manager import RulesManager
from pipeline_mco_pmsi.validators.pmsi_validator import PMSIValidator


@pytest.fixture
def mock_rag_engine():
    """Crée un mock du RAG Engine."""
    engine = MagicMock(spec=RAGEngine)
    engine.retrieve_eligibility_criteria.return_value = None
    return engine


@pytest.fixture
def model_version():
    """Crée une version de modèle de test."""
    return ModelVersion(
        model_name="test-model",
        model_tag="v1.0",
        model_digest="a" * 64,
    )


@pytest.fixture
def rules_manager_with_default_rules():
    """Crée un RulesManager avec les règles par défaut."""
    manager = RulesManager()
    manager.create_default_ruleset()
    return manager


@pytest.fixture
def sample_stay():
    """Crée un séjour de test."""
    doc = ClinicalDocument(
        document_id="doc1",
        document_type="cr_operatoire",
        content="Patient avec diabète de type 2.",
        metadata={},
        creation_date=datetime.now(),
        priority=1,
    )
    
    fact = ClinicalFact(
        fact_id="fact1",
        type="diagnostic",
        text="diabète de type 2",
        qualifier=Qualifier(
            certainty="affirmé",
            negation=False,
            temporality="actuel",
            confidence=0.9,
        ),
        evidence=Evidence(
            document_id="doc1",
            span=Span(start=13, end=30),
            text="diabète de type 2",
        ),
        temporality="actuel",
        confidence=0.9,
    )
    
    return StructuredStay(
        stay_id="stay1",
        documents=[doc],
        sections=[],
        facts=[fact],
    )


@pytest.fixture
def sample_proposal(model_version):
    """Crée une proposition de codage de test."""
    dp = Code(
        code="E11.9",
        type="dp",
        label="Diabète sucré de type 2, sans complication",
        confidence=0.9,
        reasoning="Diabète de type 2 mentionné dans le CRO",
        evidence=[
            Evidence(
                document_id="doc1",
                span=Span(start=13, end=30),
                text="diabète de type 2",
            )
        ],
        referentiel_version="2026",
    )
    
    return CodingProposal(
        stay_id="stay1",
        dp=dp,
        dr=None,
        das=[],
        ccam=[],
        reasoning="Proposition de test",
        model_version=model_version,
        prompt_version="test-prompt-1.0",
    )


def test_pmsi_validator_without_rules_manager(mock_rag_engine, sample_proposal, sample_stay):
    """
    Test que le PMSIValidator fonctionne sans RulesManager.
    
    Exigences: 20.4
    """
    validator = PMSIValidator(rag_engine=mock_rag_engine)
    
    issues = validator.validate_proposal(sample_proposal, sample_stay)
    
    # Devrait fonctionner sans erreur
    assert isinstance(issues, list)


def test_pmsi_validator_with_rules_manager(
    mock_rag_engine,
    rules_manager_with_default_rules,
    sample_proposal,
    sample_stay,
):
    """
    Test que le PMSIValidator applique les règles du RulesManager.
    
    Exigences: 20.4
    """
    validator = PMSIValidator(
        rag_engine=mock_rag_engine,
        rules_manager=rules_manager_with_default_rules,
    )
    
    issues = validator.validate_proposal(sample_proposal, sample_stay)
    
    # Devrait fonctionner et appliquer les règles
    assert isinstance(issues, list)


def test_rule_dp_with_evidence_applied(
    mock_rag_engine,
    rules_manager_with_default_rules,
    model_version,
    sample_stay,
):
    """
    Test que la règle 'DP avec preuves' est appliquée.
    
    Exigences: 20.4
    """
    # Créer une proposition avec DP qui a une preuve minimale
    # (Code requires at least 1 evidence)
    dp_with_minimal_evidence = Code(
        code="E11.9",
        type="dp",
        label="Diabète sucré de type 2",
        confidence=0.9,
        reasoning="Test",
        evidence=[
            Evidence(
                document_id="doc1",
                span=Span(start=0, end=1),
                text="x",  # Preuve minimale
            )
        ],
        referentiel_version="2026",
    )
    
    proposal = CodingProposal(
        stay_id="stay1",
        dp=dp_with_minimal_evidence,
        dr=None,
        das=[],
        ccam=[],
        reasoning="Test",
        model_version=model_version,
        prompt_version="test-prompt-1.0",
    )
    
    validator = PMSIValidator(
        rag_engine=mock_rag_engine,
        rules_manager=rules_manager_with_default_rules,
    )
    
    issues = validator.validate_proposal(proposal, sample_stay)
    
    # Devrait valider sans erreur (a au moins une preuve)
    assert isinstance(issues, list)


def test_rule_negated_fact_rejected(
    mock_rag_engine,
    rules_manager_with_default_rules,
    model_version,
):
    """
    Test que la règle 'Pas de codes pour faits niés' est appliquée.
    
    Exigences: 20.4
    """
    # Créer un fait nié
    negated_fact = ClinicalFact(
        fact_id="fact1",
        type="diagnostic",
        text="diabète",
        qualifier=Qualifier(
            certainty="nié",
            negation=True,
            temporality="actuel",
            confidence=0.9,
        ),
        evidence=Evidence(
            document_id="doc1",
            span=Span(start=0, end=7),
            text="diabète",
        ),
        temporality="actuel",
        confidence=0.9,
    )
    
    stay = StructuredStay(
        stay_id="stay1",
        documents=[
            ClinicalDocument(
                document_id="doc1",
                document_type="cr_operatoire",
                content="Pas de diabète.",
                metadata={},
                creation_date=datetime.now(),
                priority=1,
            )
        ],
        sections=[],
        facts=[negated_fact],
    )
    
    # Créer une proposition qui code le fait nié
    dp = Code(
        code="E11.9",
        type="dp",
        label="Diabète",
        confidence=0.9,
        reasoning="Test",
        evidence=[
            Evidence(
                document_id="doc1",
                span=Span(start=0, end=7),
                text="diabète",
            )
        ],
        referentiel_version="2026",
    )
    
    proposal = CodingProposal(
        stay_id="stay1",
        dp=dp,
        dr=None,
        das=[],
        ccam=[],
        reasoning="Test",
        model_version=model_version,
        prompt_version="test-prompt-1.0",
    )
    
    validator = PMSIValidator(
        rag_engine=mock_rag_engine,
        rules_manager=rules_manager_with_default_rules,
    )
    
    issues = validator.validate_proposal(proposal, stay)
    
    # Devrait détecter l'erreur zéro-tolérance
    blocking_issues = [i for i in issues if i.severity == "bloquant"]
    assert len(blocking_issues) > 0


def test_conservative_mode_rules(
    mock_rag_engine,
    sample_stay,
):
    """
    Test que le mode conservateur applique des règles strictes.
    
    Exigences: 20.3
    """
    # Créer un RulesManager en mode conservateur
    manager = RulesManager()
    ruleset = manager.create_default_ruleset()
    assert ruleset.mode == "conservateur"
    
    validator = PMSIValidator(
        rag_engine=mock_rag_engine,
        rules_manager=manager,
    )
    
    # Le mode conservateur devrait être actif
    assert manager.is_conservative_mode()
    assert not manager.is_aggressive_mode()


def test_rules_version_tracking(rules_manager_with_default_rules):
    """
    Test que la version et le hash des règles sont trackés.
    
    Exigences: 20.2
    """
    version_info = rules_manager_with_default_rules.get_version_info()
    
    assert "version" in version_info
    assert "hash" in version_info
    assert "mode" in version_info
    assert "rules_count" in version_info
    
    # Le hash doit être un SHA-256 (64 caractères hex)
    assert len(version_info["hash"]) == 64
    assert all(c in "0123456789abcdef" for c in version_info["hash"])


def test_rules_by_category(rules_manager_with_default_rules):
    """
    Test la récupération de règles par catégorie.
    
    Exigences: 20.1
    """
    # Récupérer les règles DP
    dp_rules = rules_manager_with_default_rules.get_rules_by_category("dp")
    assert len(dp_rules) > 0
    assert all(rule.category == "dp" for rule in dp_rules)
    
    # Récupérer les règles de validation
    validation_rules = rules_manager_with_default_rules.get_rules_by_category("validation")
    assert len(validation_rules) > 0
    assert all(rule.category == "validation" for rule in validation_rules)
    
    # Récupérer les règles CCAM
    ccam_rules = rules_manager_with_default_rules.get_rules_by_category("ccam")
    assert len(ccam_rules) > 0
    assert all(rule.category == "ccam" for rule in ccam_rules)


def test_rule_by_id(rules_manager_with_default_rules):
    """
    Test la récupération d'une règle par son ID.
    
    Exigences: 20.1
    """
    # Récupérer une règle spécifique
    rule = rules_manager_with_default_rules.get_rule_by_id("dp_001")
    
    assert rule is not None
    assert rule.rule_id == "dp_001"
    assert rule.name == "DP obligatoire"
    
    # Règle inexistante
    nonexistent = rules_manager_with_default_rules.get_rule_by_id("nonexistent")
    assert nonexistent is None


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