"""
Validateur PMSI pour la détection de problèmes de codage.

Ce module implémente la validation PMSI qui:
- Génère des problèmes de validation catégorisés (bloquant/à_revoir/info)
- Détecte les informations obligatoires manquantes
- Valide la conformité aux critères d'éligibilité du Guide Méthodologique
- Intègre avec le RAG Engine pour récupérer les critères d'éligibilité
- Applique les règles configurables spécifiques à l'établissement

Exigences: 9.1, 9.2, 26.5, 20.4
"""

import logging
import uuid
from typing import TYPE_CHECKING, List, Optional

from pipeline_mco_pmsi.models.clinical import ClinicalFact, StructuredStay
from pipeline_mco_pmsi.models.coding import Code, CodingProposal
from pipeline_mco_pmsi.models.validation import EligibilityCriteria, ValidationIssue
from pipeline_mco_pmsi.rag.rag_engine import RAGEngine

if TYPE_CHECKING:
    from pipeline_mco_pmsi.rules.rules_manager import RulesManager

logger = logging.getLogger(__name__)


class PMSIValidator:
    """
    Validateur PMSI pour la détection de problèmes de codage.

    Responsabilités:
    - Générer des problèmes de validation catégorisés (bloquant/à_revoir/info)
    - Détecter les informations obligatoires manquantes
    - Valider la conformité aux critères d'éligibilité
    - Bloquer la validation automatique si problèmes bloquants détectés

    Exigences: 9.1, 9.2, 26.5
    """

    def __init__(self, rag_engine: RAGEngine, rules_manager: Optional['RulesManager'] = None):
        """
        Initialise le validateur PMSI.

        Args:
            rag_engine: Moteur RAG pour récupérer les critères d'éligibilité
            rules_manager: Gestionnaire de règles configurables (optionnel)
        """
        self.rag_engine = rag_engine
        self.rules_manager = rules_manager
        logger.info(f"PMSIValidator initialisé (rules_manager={'activé' if rules_manager else 'désactivé'})")

    def validate_proposal(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Valide une proposition de codage et génère les problèmes détectés.

        Args:
            proposal: Proposition de codage à valider
            structured_stay: Séjour structuré avec faits cliniques

        Returns:
            Liste de problèmes de validation détectés

        Exigences: 9.1, 9.2, 26.5
        """
        logger.info(f"Validation de la proposition pour le séjour {proposal.stay_id}")

        issues: List[ValidationIssue] = []

        # 1. Vérifier les informations obligatoires manquantes
        missing_info_issues = self._check_missing_mandatory_info(
            proposal, structured_stay
        )
        issues.extend(missing_info_issues)

        # 2. Valider la conformité aux critères d'éligibilité
        eligibility_issues = self._check_eligibility_criteria(proposal)
        issues.extend(eligibility_issues)

        # 3. Vérifier la cohérence des codes proposés
        consistency_issues = self._check_code_consistency(proposal, structured_stay)
        issues.extend(consistency_issues)

        # 4. Appliquer les règles spécifiques à l'établissement
        if self.rules_manager:
            establishment_issues = self._apply_establishment_rules(proposal, structured_stay)
            issues.extend(establishment_issues)

        logger.info(
            f"Validation terminée: {len(issues)} problèmes détectés "
            f"({self._count_by_severity(issues, 'bloquant')} bloquants, "
            f"{self._count_by_severity(issues, 'a_revoir')} à revoir, "
            f"{self._count_by_severity(issues, 'info')} info)"
        )

        return issues

    def _check_missing_mandatory_info(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les informations obligatoires manquantes.

        Informations obligatoires:
        - Diagnostic Principal (DP)
        - Au moins un document clinique
        - Preuves pour tous les codes proposés

        Args:
            proposal: Proposition de codage
            structured_stay: Séjour structuré

        Returns:
            Liste de problèmes de type "missing_info"

        Exigences: 9.2
        """
        issues: List[ValidationIssue] = []

        # Vérifier la présence du DP
        if proposal.dp is None:
            issue = ValidationIssue(
                issue_id=self._generate_issue_id(),
                severity="bloquant",
                category="missing_info",
                message="Diagnostic Principal (DP) manquant",
                affected_codes=[],
                suggested_action="Identifier et coder le diagnostic principal du séjour",
            )
            issues.append(issue)
            logger.warning(f"DP manquant pour le séjour {proposal.stay_id}")

        # Vérifier la présence de documents
        if not structured_stay.documents:
            issue = ValidationIssue(
                issue_id=self._generate_issue_id(),
                severity="bloquant",
                category="missing_info",
                message="Aucun document clinique disponible",
                affected_codes=[],
                suggested_action="Ajouter au moins un document clinique au séjour",
            )
            issues.append(issue)
            logger.error(f"Aucun document pour le séjour {proposal.stay_id}")

        # Vérifier la présence de faits cliniques
        if not structured_stay.facts:
            issue = ValidationIssue(
                issue_id=self._generate_issue_id(),
                severity="a_revoir",
                category="missing_info",
                message="Aucun fait clinique extrait des documents",
                affected_codes=[],
                suggested_action="Vérifier la qualité des documents ou l'extraction de faits",
            )
            issues.append(issue)
            logger.warning(f"Aucun fait clinique pour le séjour {proposal.stay_id}")

        # Vérifier les preuves pour tous les codes
        all_codes = []
        if proposal.dp:
            all_codes.append(proposal.dp)
        if proposal.dr:
            all_codes.append(proposal.dr)
        all_codes.extend(proposal.das)
        all_codes.extend(proposal.ccam)

        for code in all_codes:
            if not code.evidence or len(code.evidence) == 0:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="bloquant",
                    category="missing_info",
                    message=f"Code {code.code} sans preuve textuelle",
                    affected_codes=[code.code],
                    suggested_action="Fournir au moins une preuve textuelle pour ce code",
                )
                issues.append(issue)
                logger.error(f"Code {code.code} sans preuve")

        return issues

    def _check_eligibility_criteria(
        self,
        proposal: CodingProposal,
    ) -> List[ValidationIssue]:
        """
        Valide la conformité aux critères d'éligibilité du Guide Méthodologique.

        Pour chaque code DP et DAS, récupère les critères d'éligibilité via le
        RAG Engine et vérifie la conformité.

        Args:
            proposal: Proposition de codage

        Returns:
            Liste de problèmes de type "eligibility"

        Exigences: 26.5, 26.6
        """
        issues: List[ValidationIssue] = []

        # Vérifier le DP
        if proposal.dp:
            dp_issues = self._validate_code_eligibility(proposal.dp, "dp")
            issues.extend(dp_issues)

        # Vérifier les DAS
        for das_code in proposal.das:
            das_issues = self._validate_code_eligibility(das_code, "das")
            issues.extend(das_issues)

        return issues

    def _validate_code_eligibility(
        self,
        code: Code,
        code_type: str,
    ) -> List[ValidationIssue]:
        """
        Valide l'éligibilité d'un code spécifique.

        Args:
            code: Code à valider
            code_type: Type de code ("dp" ou "das")

        Returns:
            Liste de problèmes d'éligibilité détectés

        Exigences: 26.5, 26.6
        """
        issues: List[ValidationIssue] = []

        try:
            # Récupérer les critères d'éligibilité via le RAG Engine
            criteria = self.rag_engine.retrieve_eligibility_criteria(
                code.code, code_type
            )

            if criteria is None:
                # Aucun critère trouvé - générer un avertissement
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="info",
                    category="eligibility",
                    message=f"Aucun critère d'éligibilité trouvé pour {code.code} ({code_type.upper()})",
                    affected_codes=[code.code],
                    suggested_action="Vérifier manuellement l'éligibilité dans le Guide Méthodologique",
                )
                issues.append(issue)
                logger.info(f"Aucun critère trouvé pour {code.code}")
                return issues

            # Analyser les critères d'éligibilité
            # Note: L'analyse détaillée des critères nécessiterait un LLM
            # Pour l'instant, on génère un avertissement si des exclusions existent
            if criteria.exclusions:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="a_revoir",
                    category="eligibility",
                    message=f"Code {code.code} a des règles d'exclusion à vérifier",
                    affected_codes=[code.code],
                    suggested_action=f"Vérifier les exclusions: {', '.join(criteria.exclusions[:3])}",
                )
                issues.append(issue)
                logger.info(
                    f"Règles d'exclusion trouvées pour {code.code}: "
                    f"{len(criteria.exclusions)} règles"
                )

            # Vérifier les règles de hiérarchisation
            if criteria.hierarchization:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="info",
                    category="eligibility",
                    message=f"Code {code.code} a des règles de hiérarchisation",
                    affected_codes=[code.code],
                    suggested_action=f"Vérifier la hiérarchisation: {', '.join(criteria.hierarchization[:2])}",
                )
                issues.append(issue)
                logger.info(
                    f"Règles de hiérarchisation trouvées pour {code.code}: "
                    f"{len(criteria.hierarchization)} règles"
                )

        except Exception as e:
            logger.error(f"Erreur lors de la validation d'éligibilité pour {code.code}: {e}")
            issue = ValidationIssue(
                issue_id=self._generate_issue_id(),
                severity="info",
                category="eligibility",
                message=f"Erreur lors de la vérification d'éligibilité pour {code.code}",
                affected_codes=[code.code],
                suggested_action="Vérifier manuellement l'éligibilité",
            )
            issues.append(issue)

        return issues

    def _check_code_consistency(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Vérifie la cohérence des codes proposés avec les faits cliniques.

        Détecte:
        - Codes sans faits cliniques correspondants
        - Faits cliniques importants non codés
        - Incohérences entre types de codes

        Args:
            proposal: Proposition de codage
            structured_stay: Séjour structuré

        Returns:
            Liste de problèmes de cohérence

        Exigences: 9.4
        """
        issues: List[ValidationIssue] = []

        # Vérifier que chaque code a un fait clinique correspondant
        all_codes = []
        if proposal.dp:
            all_codes.append(proposal.dp)
        if proposal.dr:
            all_codes.append(proposal.dr)
        all_codes.extend(proposal.das)
        all_codes.extend(proposal.ccam)

        for code in all_codes:
            # Vérifier que les preuves du code correspondent à des faits du séjour
            has_matching_fact = False
            for evidence in code.evidence:
                # Chercher un fait avec le même document_id
                for fact in structured_stay.facts:
                    if fact.evidence.document_id == evidence.document_id:
                        has_matching_fact = True
                        break
                if has_matching_fact:
                    break

            if not has_matching_fact:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="a_revoir",
                    category="contradiction",
                    message=f"Code {code.code} sans fait clinique correspondant dans le séjour",
                    affected_codes=[code.code],
                    suggested_action="Vérifier que le code est justifié par les documents du séjour",
                )
                issues.append(issue)
                logger.warning(f"Code {code.code} sans fait correspondant")

        # Vérifier les faits diagnostiques non codés
        diagnostic_facts = [
            f for f in structured_stay.facts
            if f.type == "diagnostic" and f.qualifier.certainty == "affirmé"
        ]

        coded_texts = set()
        for code in all_codes:
            for evidence in code.evidence:
                coded_texts.add(evidence.text.lower())

        for fact in diagnostic_facts:
            if fact.text.lower() not in coded_texts:
                # Fait diagnostique potentiellement non codé
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="info",
                    category="missing_info",
                    message=f"Diagnostic '{fact.text}' potentiellement non codé",
                    affected_codes=[],
                    suggested_action="Vérifier si ce diagnostic doit être codé comme DAS",
                )
                issues.append(issue)
                logger.info(f"Diagnostic non codé détecté: {fact.text}")

        return issues
    def _apply_establishment_rules(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Applique les règles spécifiques à l'établissement.

        Args:
            proposal: Proposition de codage
            structured_stay: Séjour structuré

        Returns:
            Liste de problèmes détectés par les règles

        Exigences: 20.4
        """
        issues: List[ValidationIssue] = []

        if not self.rules_manager or not self.rules_manager.current_ruleset:
            logger.debug("Aucune règle d'établissement configurée")
            return issues

        logger.info(
            f"Application de {len(self.rules_manager.current_ruleset.rules)} règles d'établissement "
            f"(mode={self.rules_manager.current_ruleset.mode})"
        )

        # Appliquer les règles par catégorie
        all_codes = []
        if proposal.dp:
            all_codes.append(("dp", proposal.dp))
        if proposal.dr:
            all_codes.append(("dr", proposal.dr))
        for das in proposal.das:
            all_codes.append(("das", das))
        for ccam in proposal.ccam:
            all_codes.append(("ccam", ccam))

        # Appliquer les règles de validation générale
        validation_rules = self.rules_manager.get_rules_by_category("validation")
        for rule in validation_rules:
            # Vérifier si la règle s'applique
            context = {
                "proposal": proposal,
                "structured_stay": structured_stay,
                "facts": structured_stay.facts,
            }

            if self.rules_manager.apply_rule(rule, context):
                # Appliquer la règle selon son action
                rule_issues = self._apply_single_rule(rule, proposal, structured_stay)
                issues.extend(rule_issues)

        # Appliquer les règles spécifiques par type de code
        for code_type, code in all_codes:
            category_rules = self.rules_manager.get_rules_by_category(code_type)

            for rule in category_rules:
                context = {
                    "code": code,
                    "code_type": code_type,
                    "proposal": proposal,
                    "structured_stay": structured_stay,
                }

                if self.rules_manager.apply_rule(rule, context):
                    rule_issues = self._apply_single_rule(rule, proposal, structured_stay, code)
                    issues.extend(rule_issues)

        logger.info(f"Règles d'établissement: {len(issues)} problèmes détectés")
        return issues

    def _apply_single_rule(
        self,
        rule,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
        code: Optional[Code] = None,
    ) -> List[ValidationIssue]:
        """
        Applique une règle individuelle et génère les problèmes correspondants.

        Args:
            rule: Règle à appliquer
            proposal: Proposition de codage
            structured_stay: Séjour structuré
            code: Code spécifique concerné (optionnel)

        Returns:
            Liste de problèmes générés par la règle
        """
        issues: List[ValidationIssue] = []

        # Mapper la sévérité de la règle
        severity_map = {
            "bloquant": "bloquant",
            "à_revoir": "a_revoir",
            "info": "info",
        }
        severity = severity_map.get(rule.severity, "info")

        # Générer un problème selon l'action de la règle
        if rule.action == "reject_if_missing":
            # Vérifier si quelque chose est manquant
            if code and (not code.evidence or len(code.evidence) == 0):
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity=severity,
                    category="missing_info",
                    message=f"Règle '{rule.name}': {rule.description}",
                    affected_codes=[code.code] if code else [],
                    suggested_action=f"Action requise: {rule.action}",
                )
                issues.append(issue)

        elif rule.action == "reject_code":
            # Rejeter un code spécifique
            if code:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity=severity,
                    category="dim_error",
                    message=f"Règle '{rule.name}': {rule.description} - Code {code.code} rejeté",
                    affected_codes=[code.code],
                    suggested_action="Retirer ce code ou vérifier les conditions",
                )
                issues.append(issue)

        elif rule.action == "reject_as_dp":
            # Rejeter comme DP
            if code and proposal.dp and code.code == proposal.dp.code:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity=severity,
                    category="dim_error",
                    message=f"Règle '{rule.name}': {rule.description} - Code {code.code} ne peut pas être DP",
                    affected_codes=[code.code],
                    suggested_action="Choisir un autre code comme DP",
                )
                issues.append(issue)

        elif rule.action == "reject_if_insufficient":
            # Vérifier si les preuves sont insuffisantes
            if code:
                min_evidence = rule.condition.get("min_evidence", 1)
                if not code.evidence or len(code.evidence) < min_evidence:
                    issue = ValidationIssue(
                        issue_id=self._generate_issue_id(),
                        severity=severity,
                        category="missing_info",
                        message=f"Règle '{rule.name}': {rule.description} - Code {code.code} a {len(code.evidence) if code.evidence else 0} preuve(s), minimum {min_evidence} requis",
                        affected_codes=[code.code],
                        suggested_action=f"Fournir au moins {min_evidence} preuve(s) pour ce code",
                    )
                    issues.append(issue)

        else:
            # Action générique
            logger.debug(f"Action de règle non implémentée: {rule.action}")

        return issues

    def has_blocking_issues(self, issues: List[ValidationIssue]) -> bool:
        """
        Vérifie si des problèmes bloquants sont présents.

        Args:
            issues: Liste de problèmes de validation

        Returns:
            True si au moins un problème bloquant est présent

        Exigences: 9.6
        """
        return any(issue.severity == "bloquant" for issue in issues)

    def check_zero_tolerance_errors(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les erreurs zéro-tolérance qui bloquent la validation automatique.

        Erreurs zéro-tolérance:
        - Diagnostic nié codé comme affirmé
        - Suspicion transformée en certitude
        - Acte CCAM sans preuve explicite
        - Antécédent codé comme épisode actuel
        - Inversion grossière DP/DAS
        - Version référentiel inconnue
        - DIP dans logs/exports
        - Confiance élevée sur cas ambigu

        Args:
            proposal: Proposition de codage
            structured_stay: Séjour structuré

        Returns:
            Liste de problèmes zéro-tolérance détectés

        Exigences: 19.1, 19.2, 19.3, 19.4, 19.5, 19.6, 19.9
        """
        logger.info(f"Vérification des erreurs zéro-tolérance pour {proposal.stay_id}")

        issues: List[ValidationIssue] = []

        # 1. Vérifier les diagnostics niés codés comme affirmés
        negated_coded_issues = self._check_negated_coded_as_affirmed(
            proposal, structured_stay
        )
        issues.extend(negated_coded_issues)

        # 2. Vérifier les suspicions transformées en certitude
        suspected_coded_issues = self._check_suspected_coded_as_certain(
            proposal, structured_stay
        )
        issues.extend(suspected_coded_issues)

        # 3. Vérifier les actes CCAM sans preuve explicite
        ccam_no_evidence_issues = self._check_ccam_without_evidence(proposal)
        issues.extend(ccam_no_evidence_issues)

        # 4. Vérifier les antécédents codés comme épisode actuel
        history_as_current_issues = self._check_history_as_current(
            proposal, structured_stay
        )
        issues.extend(history_as_current_issues)

        # 5. Vérifier les versions de référentiels
        unknown_version_issues = self._check_unknown_referentiel_version(proposal)
        issues.extend(unknown_version_issues)

        # 6. Vérifier la confiance élevée sur cas ambigus
        high_confidence_ambiguous_issues = self._check_high_confidence_ambiguous(
            proposal, structured_stay
        )
        issues.extend(high_confidence_ambiguous_issues)

        logger.info(
            f"Erreurs zéro-tolérance détectées: {len(issues)} "
            f"({', '.join(i.message[:50] for i in issues[:3])}...)"
        )

        return issues

    def _check_negated_coded_as_affirmed(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les diagnostics niés codés comme affirmés.

        Exigences: 19.1
        """
        issues: List[ValidationIssue] = []

        negated_facts = [
            f for f in structured_stay.facts
            if f.type == "diagnostic" and f.qualifier.certainty == "nié"
        ]

        all_codes = []
        if proposal.dp:
            all_codes.append(proposal.dp)
        if proposal.dr:
            all_codes.append(proposal.dr)
        all_codes.extend(proposal.das)

        for fact in negated_facts:
            for code in all_codes:
                # Vérifier si le code correspond au fait nié
                for evidence in code.evidence:
                    if (
                        evidence.document_id == fact.evidence.document_id
                        and fact.text.lower() in evidence.text.lower()
                    ):
                        issue = ValidationIssue(
                            issue_id=self._generate_issue_id(),
                            severity="bloquant",
                            category="dim_error",
                            message=f"ERREUR ZÉRO-TOLÉRANCE: Diagnostic nié '{fact.text}' codé comme {code.type.upper()} {code.code}",
                            affected_codes=[code.code],
                            suggested_action="Retirer ce code ou clarifier la négation dans les documents",
                        )
                        issues.append(issue)
                        logger.error(
                            f"Erreur zéro-tolérance: diagnostic nié '{fact.text}' "
                            f"codé comme {code.code}"
                        )

        return issues

    def _check_suspected_coded_as_certain(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les suspicions transformées en certitude.

        Exigences: 19.2
        """
        issues: List[ValidationIssue] = []

        suspected_facts = [
            f for f in structured_stay.facts
            if f.type == "diagnostic" and f.qualifier.certainty == "suspecté"
        ]

        # Vérifier particulièrement le DP (ne doit jamais être suspecté)
        if proposal.dp:
            for fact in suspected_facts:
                for evidence in proposal.dp.evidence:
                    if (
                        evidence.document_id == fact.evidence.document_id
                        and fact.text.lower() in evidence.text.lower()
                    ):
                        issue = ValidationIssue(
                            issue_id=self._generate_issue_id(),
                            severity="bloquant",
                            category="dim_error",
                            message=f"ERREUR ZÉRO-TOLÉRANCE: Diagnostic suspecté '{fact.text}' codé comme DP {proposal.dp.code}",
                            affected_codes=[proposal.dp.code],
                            suggested_action="Le DP ne peut pas être un diagnostic suspecté. Attendre confirmation ou choisir un autre DP.",
                        )
                        issues.append(issue)
                        logger.error(
                            f"Erreur zéro-tolérance: diagnostic suspecté '{fact.text}' "
                            f"codé comme DP {proposal.dp.code}"
                        )

        return issues

    def _check_ccam_without_evidence(
        self,
        proposal: CodingProposal,
    ) -> List[ValidationIssue]:
        """
        Détecte les actes CCAM sans preuve explicite.

        Exigences: 19.3
        """
        issues: List[ValidationIssue] = []

        for ccam_code in proposal.ccam:
            if not ccam_code.evidence or len(ccam_code.evidence) == 0:
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="bloquant",
                    category="dim_error",
                    message=f"ERREUR ZÉRO-TOLÉRANCE: Acte CCAM {ccam_code.code} sans preuve explicite",
                    affected_codes=[ccam_code.code],
                    suggested_action="Fournir une preuve textuelle explicite de la réalisation de l'acte",
                )
                issues.append(issue)
                logger.error(
                    f"Erreur zéro-tolérance: acte CCAM {ccam_code.code} sans preuve"
                )

        return issues

    def _check_history_as_current(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les antécédents codés comme épisode actuel.

        Exigences: 19.4
        """
        issues: List[ValidationIssue] = []

        history_facts = [
            f for f in structured_stay.facts
            if f.type == "antecedent" or f.temporality == "antecedent"
        ]

        # Vérifier particulièrement le DP (ne doit jamais être un antécédent)
        if proposal.dp:
            for fact in history_facts:
                for evidence in proposal.dp.evidence:
                    if (
                        evidence.document_id == fact.evidence.document_id
                        and fact.text.lower() in evidence.text.lower()
                    ):
                        issue = ValidationIssue(
                            issue_id=self._generate_issue_id(),
                            severity="bloquant",
                            category="dim_error",
                            message=f"ERREUR ZÉRO-TOLÉRANCE: Antécédent '{fact.text}' codé comme DP {proposal.dp.code}",
                            affected_codes=[proposal.dp.code],
                            suggested_action="Le DP doit être un diagnostic de l'épisode actuel, pas un antécédent.",
                        )
                        issues.append(issue)
                        logger.error(
                            f"Erreur zéro-tolérance: antécédent '{fact.text}' "
                            f"codé comme DP {proposal.dp.code}"
                        )

        return issues

    def _check_unknown_referentiel_version(
        self,
        proposal: CodingProposal,
    ) -> List[ValidationIssue]:
        """
        Détecte les codes avec version de référentiel inconnue.

        Exigences: 19.6
        """
        issues: List[ValidationIssue] = []

        all_codes = []
        if proposal.dp:
            all_codes.append(proposal.dp)
        if proposal.dr:
            all_codes.append(proposal.dr)
        all_codes.extend(proposal.das)
        all_codes.extend(proposal.ccam)

        for code in all_codes:
            if not code.referentiel_version or code.referentiel_version == "unknown":
                issue = ValidationIssue(
                    issue_id=self._generate_issue_id(),
                    severity="bloquant",
                    category="dim_error",
                    message=f"ERREUR ZÉRO-TOLÉRANCE: Code {code.code} avec version de référentiel inconnue",
                    affected_codes=[code.code],
                    suggested_action="Vérifier que le référentiel est correctement chargé et versionné",
                )
                issues.append(issue)
                logger.error(
                    f"Erreur zéro-tolérance: code {code.code} "
                    f"avec version référentiel inconnue"
                )

        return issues

    def _check_high_confidence_ambiguous(
        self,
        proposal: CodingProposal,
        structured_stay: StructuredStay,
    ) -> List[ValidationIssue]:
        """
        Détecte les cas ambigus avec confiance élevée.

        Un cas est considéré ambigu si:
        - Plusieurs faits contradictoires existent
        - Des faits suspectés ou niés sont présents
        - Peu de preuves disponibles

        Exigences: 19.8
        """
        issues: List[ValidationIssue] = []

        # Compter les faits non affirmés
        non_affirmed_facts = [
            f for f in structured_stay.facts
            if f.qualifier.certainty != "affirmé"
        ]

        # Si beaucoup de faits non affirmés et confiance élevée, c'est suspect
        if len(non_affirmed_facts) > len(structured_stay.facts) * 0.3:
            # Plus de 30% de faits non affirmés
            all_codes = []
            if proposal.dp:
                all_codes.append(proposal.dp)
            if proposal.dr:
                all_codes.append(proposal.dr)
            all_codes.extend(proposal.das)

            for code in all_codes:
                if code.confidence > 0.8:
                    issue = ValidationIssue(
                        issue_id=self._generate_issue_id(),
                        severity="bloquant",
                        category="dim_error",
                        message=f"ERREUR ZÉRO-TOLÉRANCE: Confiance élevée ({code.confidence:.2f}) sur cas ambigu pour {code.code}",
                        affected_codes=[code.code],
                        suggested_action=f"Revoir la confiance du code {code.code} compte tenu de l'ambiguïté du cas",
                    )
                    issues.append(issue)
                    logger.error(
                        f"Erreur zéro-tolérance: confiance élevée ({code.confidence:.2f}) "
                        f"sur cas ambigu pour {code.code}"
                    )

        return issues

    def should_block_automatic_validation(
        self,
        validation_issues: List[ValidationIssue],
        zero_tolerance_issues: List[ValidationIssue],
    ) -> bool:
        """
        Détermine si la validation automatique doit être bloquée.

        La validation est bloquée si:
        - Au moins un problème bloquant est présent
        - Au moins une erreur zéro-tolérance est présente

        Args:
            validation_issues: Problèmes de validation standard
            zero_tolerance_issues: Erreurs zéro-tolérance

        Returns:
            True si la validation automatique doit être bloquée

        Exigences: 9.6, 19.9
        """
        has_blocking = self.has_blocking_issues(validation_issues)
        has_zero_tolerance = len(zero_tolerance_issues) > 0

        should_block = has_blocking or has_zero_tolerance

        if should_block:
            logger.warning(
                f"Validation automatique bloquée: "
                f"{len([i for i in validation_issues if i.severity == 'bloquant'])} problèmes bloquants, "
                f"{len(zero_tolerance_issues)} erreurs zéro-tolérance"
            )

        return should_block

    def _count_by_severity(
        self, issues: List[ValidationIssue], severity: str
    ) -> int:
        """
        Compte le nombre de problèmes d'une sévérité donnée.

        Args:
            issues: Liste de problèmes
            severity: Sévérité à compter

        Returns:
            Nombre de problèmes de cette sévérité
        """
        return sum(1 for issue in issues if issue.severity == severity)

    def _generate_issue_id(self) -> str:
        """
        Génère un identifiant unique pour un problème de validation.

        Returns:
            Identifiant unique (UUID)
        """
        return f"issue_{uuid.uuid4().hex[:12]}"
