"""
Audit Logger - Enregistrement complet des décisions de codage.

Ce module implémente l'AuditLogger qui fournit une traçabilité complète pour
toutes les décisions de codage, incluant les documents d'entrée, les faits
extraits, les codes proposés, les décisions du Vérificateur, et les corrections TIM.

Exigences : 5.1, 5.2, 5.3, 5.5, 5.6, 5.7, 5.8
"""

import hashlib
import json
from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from pipeline_mco_pmsi.database.models import (
    AuditRecordDB,
    ClinicalDocumentDB,
    ClinicalFactDB,
    CodeDB,
    GroupageResultDB,
    QuestionDB,
    StayDB,
    TIMCorrectionDB,
    ValidationIssueDB,
    VerificationResultDB,
)
from pipeline_mco_pmsi.models.clinical import ClinicalDocument, ClinicalFact, StructuredStay
from pipeline_mco_pmsi.models.coding import CodingProposal, VerificationResult
from pipeline_mco_pmsi.models.metadata import AuditRecord, StayMetadata, VersionInfo
from pipeline_mco_pmsi.models.validation import GroupageResult, Question, ValidationIssue
from pipeline_mco_pmsi.processors.pii_protector import PIIProtector


class AuditTrail(BaseModel):
    """Piste d'audit complète pour un séjour."""

    stay_id: str = Field(..., description="Identifiant du séjour")
    stay_metadata: StayMetadata = Field(..., description="Métadonnées du séjour")
    documents: List[ClinicalDocument] = Field(..., description="Documents d'entrée")
    facts: List[ClinicalFact] = Field(..., description="Faits cliniques extraits")
    coding_proposal: Optional[CodingProposal] = Field(None, description="Proposition de codage")
    verification_result: Optional[VerificationResult] = Field(
        None, description="Résultat de vérification"
    )
    validation_issues: List[ValidationIssue] = Field(
        default_factory=list, description="Problèmes de validation"
    )
    questions: List[Question] = Field(default_factory=list, description="Questions générées")
    groupage_result: Optional[GroupageResult] = Field(None, description="Résultat de groupage")
    tim_corrections: List[Dict[str, Any]] = Field(
        default_factory=list, description="Corrections TIM"
    )
    audit_records: List[AuditRecord] = Field(
        default_factory=list, description="Enregistrements d'audit"
    )
    versions: VersionInfo = Field(..., description="Informations de version")
    export_date: datetime = Field(
        default_factory=datetime.utcnow, description="Date d'export de l'audit"
    )


class AuditLogger:
    """
    Logger d'audit pour traçabilité complète des décisions de codage.

    Enregistre tous les éléments nécessaires pour l'auditabilité :
    - Documents d'entrée avec métadonnées
    - Faits cliniques extraits avec preuves
    - Codes proposés avec justifications
    - Décisions du Vérificateur
    - Corrections TIM
    - Versions de tous les composants

    Exigences : 5.1, 5.2, 5.3, 5.5, 5.6, 5.7, 5.8
    """

    def __init__(self, db_session: Session, pii_protector: Optional[PIIProtector] = None):
        """
        Initialise l'AuditLogger.

        Args:
            db_session: Session SQLAlchemy pour persistance
            pii_protector: Protecteur de DIP pour filtrage des exports (optionnel)
        """
        self.db_session = db_session
        self.pii_protector = pii_protector or PIIProtector()

    def log_coding_decision(
        self,
        stay_id: str,
        proposal: CodingProposal,
        verification: VerificationResult,
        versions: VersionInfo,
    ) -> AuditRecord:
        """
        Enregistre une décision de codage complète.

        Enregistre la proposition du Codeur, le résultat de la vérification,
        et toutes les informations de version pour reproductibilité.

        Args:
            stay_id: Identifiant du séjour
            proposal: Proposition de codage du Codeur
            verification: Résultat de la vérification
            versions: Informations de version complètes

        Returns:
            AuditRecord créé

        Exigences : 5.1, 5.3, 5.5, 5.7
        """
        # Créer l'enregistrement d'audit
        record_id = self._generate_record_id(stay_id, "coding")

        # Préparer les données de l'événement
        data = {
            "proposal": {
                "dp": proposal.dp.model_dump() if proposal.dp else None,
                "dr": proposal.dr.model_dump() if proposal.dr else None,
                "das": [code.model_dump() for code in proposal.das],
                "ccam": [code.model_dump() for code in proposal.ccam],
                "reasoning": proposal.reasoning,
            },
            "verification": {
                "decision": verification.decision,
                "dim_errors": [error.model_dump() for error in verification.dim_errors],
                "contradictions": verification.contradictions,
                "alternatives": [alt.model_dump() for alt in verification.alternatives],
                "reasoning": verification.reasoning,
            },
        }

        # Créer l'AuditRecord Pydantic
        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="coding",
            actor="system",
            data=data,
            versions=versions,
        )

        # Persister dans la base de données
        self._persist_audit_record(audit_record)

        return audit_record

    def log_tim_correction(
        self,
        stay_id: str,
        original_code: str,
        corrected_code: str,
        user_id: str,
        comment: Optional[str] = None,
        versions: Optional[VersionInfo] = None,
    ) -> AuditRecord:
        """
        Enregistre une correction TIM avec horodatage et identifiant utilisateur.

        Args:
            stay_id: Identifiant du séjour
            original_code: Code original proposé
            corrected_code: Code corrigé par le TIM
            user_id: Identifiant de l'utilisateur TIM
            comment: Commentaire optionnel
            versions: Informations de version (optionnel)

        Returns:
            AuditRecord créé

        Exigences : 5.6
        """
        # Créer l'enregistrement d'audit
        record_id = self._generate_record_id(stay_id, "correction")

        # Préparer les données de l'événement
        data = {
            "original_code": original_code,
            "corrected_code": corrected_code,
            "comment": comment,
        }

        # Créer l'AuditRecord Pydantic
        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="correction",
            actor=f"tim_{user_id}",
            data=data,
            versions=versions or self._get_default_version_info(),
        )

        # Persister dans la base de données
        self._persist_audit_record(audit_record)

        return audit_record

    def log_validation(
        self,
        stay_id: str,
        user_id: str,
        validation_status: str,
        comment: Optional[str] = None,
        versions: Optional[VersionInfo] = None,
    ) -> AuditRecord:
        """
        Enregistre une validation TIM avec horodatage et identifiant utilisateur.

        Args:
            stay_id: Identifiant du séjour
            user_id: Identifiant de l'utilisateur TIM
            validation_status: Statut de validation (accepted, rejected, etc.)
            comment: Commentaire optionnel
            versions: Informations de version (optionnel)

        Returns:
            AuditRecord créé

        Exigences : 10.7
        """
        # Créer l'enregistrement d'audit
        record_id = self._generate_record_id(stay_id, "validation")

        # Préparer les données de l'événement
        data = {
            "validation_status": validation_status,
            "comment": comment,
        }

        # Créer l'AuditRecord Pydantic
        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="validation",
            actor=f"tim_{user_id}",
            data=data,
            versions=versions or self._get_default_version_info(),
        )

        # Persister dans la base de données
        self._persist_audit_record(audit_record)

        return audit_record

    def export_audit_trail(
        self, stay_id: str, include_pii: bool = False, encrypt: bool = True
    ) -> AuditTrail:
        """
        Exporte la piste d'audit complète pour un séjour avec filtrage DIP optionnel.

        Récupère tous les éléments enregistrés pour un séjour et les assemble
        en une piste d'audit complète. Si include_pii=False, les DIP sont
        filtrées des textes exportés. Si encrypt=True, les données sont chiffrées.

        Args:
            stay_id: Identifiant du séjour
            include_pii: Inclure les DIP dans l'export (False par défaut)
            encrypt: Chiffrer les données exportées (True par défaut)

        Returns:
            AuditTrail complet pour le séjour

        Exigences : 5.8, 5.10, 11.4, 17.4
        """
        # Récupérer le séjour
        stay_db = (
            self.db_session.query(StayDB).filter(StayDB.stay_id == stay_id).first()
        )
        if not stay_db:
            raise ValueError(f"Stay {stay_id} not found")

        # Récupérer les métadonnées du séjour
        stay_metadata = StayMetadata(
            stay_id=stay_db.stay_id,
            admission_date=stay_db.admission_date,
            discharge_date=stay_db.discharge_date,
            specialty=stay_db.specialty,
            unit=stay_db.unit,
            age=stay_db.age,
            sex=stay_db.sex,
        )

        # Récupérer les documents
        documents = self._load_documents(stay_db, include_pii)

        # Récupérer les faits cliniques
        facts = self._load_facts(stay_db, include_pii)

        # Récupérer la proposition de codage
        coding_proposal = self._load_coding_proposal(stay_db, include_pii)

        # Récupérer le résultat de vérification
        verification_result = self._load_verification_result(stay_db, include_pii)

        # Récupérer les problèmes de validation
        validation_issues = self._load_validation_issues(stay_db, include_pii)

        # Récupérer les questions
        questions = self._load_questions(stay_db, include_pii)

        # Récupérer le résultat de groupage
        groupage_result = self._load_groupage_result(stay_db)

        # Récupérer les corrections TIM
        tim_corrections = self._load_tim_corrections(stay_db)

        # Récupérer les enregistrements d'audit
        audit_records = self._load_audit_records(stay_db, include_pii)

        # Récupérer les informations de version (depuis le dernier audit record)
        versions = audit_records[-1].versions if audit_records else self._get_default_version_info()

        # Créer l'AuditTrail
        audit_trail = AuditTrail(
            stay_id=stay_id,
            stay_metadata=stay_metadata,
            documents=documents,
            facts=facts,
            coding_proposal=coding_proposal,
            verification_result=verification_result,
            validation_issues=validation_issues,
            questions=questions,
            groupage_result=groupage_result,
            tim_corrections=tim_corrections,
            audit_records=audit_records,
            versions=versions,
        )

        return audit_trail

    def export_audit_trail_encrypted(
        self,
        stay_id: str,
        encryption_key: "EncryptionKey",
        include_pii: bool = False,
    ) -> bytes:
        """
        Exporte la piste d'audit complète chiffrée pour un séjour.

        Args:
            stay_id: Identifiant du séjour
            encryption_key: Clé de chiffrement
            include_pii: Inclure les DIP dans l'export (False par défaut)

        Returns:
            Données d'audit chiffrées (bytes)

        Exigences : 17.4
        """
        from pipeline_mco_pmsi.security.encryption import AuditEncryptor

        # Exporter l'audit trail
        audit_trail = self.export_audit_trail(stay_id, include_pii, encrypt=False)

        # Convertir en dict
        audit_data = audit_trail.model_dump(mode="json")

        # Chiffrer
        encryptor = AuditEncryptor(encryption_key)
        encrypted_data = encryptor.encrypt_audit_data(audit_data)

        return encrypted_data

    # Helper methods for loading data

    def _load_documents(
        self, stay_db: StayDB, include_pii: bool
    ) -> List[ClinicalDocument]:
        """Charge les documents cliniques avec filtrage DIP optionnel."""
        documents = []
        for doc_db in stay_db.documents:
            content = doc_db.content
            if not include_pii:
                content = self.pii_protector.anonymize_text(content)

            documents.append(
                ClinicalDocument(
                    document_id=doc_db.document_id,
                    document_type=doc_db.document_type,
                    content=content,
                    creation_date=doc_db.creation_date,
                    author=doc_db.author,
                    priority=doc_db.priority,
                )
            )
        return documents

    def _load_facts(self, stay_db: StayDB, include_pii: bool) -> List[ClinicalFact]:
        """Charge les faits cliniques avec filtrage DIP optionnel."""
        from pipeline_mco_pmsi.models.clinical import Evidence, Qualifier, Span

        facts = []
        for fact_db in stay_db.facts:
            text = fact_db.text
            evidence_text = fact_db.evidence_text
            evidence_context = fact_db.evidence_context

            if not include_pii:
                text = self.pii_protector.anonymize_text(text)
                evidence_text = self.pii_protector.anonymize_text(evidence_text)
                if evidence_context:
                    evidence_context = self.pii_protector.anonymize_text(evidence_context)

            facts.append(
                ClinicalFact(
                    fact_id=fact_db.fact_id,
                    type=fact_db.type,
                    text=text,
                    qualifier=Qualifier(
                        certainty=fact_db.qualifier_certainty,
                        markers=fact_db.qualifier_markers or [],
                        confidence=fact_db.qualifier_confidence,
                    ),
                    temporality=fact_db.temporality,
                    evidence=Evidence(
                        document_id=fact_db.evidence_document_id,
                        span=Span(
                            start=fact_db.evidence_span_start,
                            end=fact_db.evidence_span_end,
                        ),
                        text=evidence_text,
                        context=evidence_context,
                    ),
                    confidence=fact_db.confidence,
                )
            )
        return facts

    def _load_coding_proposal(
        self, stay_db: StayDB, include_pii: bool
    ) -> Optional[CodingProposal]:
        """Charge la proposition de codage avec filtrage DIP optionnel."""
        from pipeline_mco_pmsi.models.clinical import Evidence, Span
        from pipeline_mco_pmsi.models.coding import Code
        from pipeline_mco_pmsi.models.metadata import ModelVersion

        # Récupérer les codes proposés
        codes_db = [c for c in stay_db.codes if c.status == "proposed"]
        if not codes_db:
            return None

        # Construire les codes avec leurs preuves
        dp = None
        dr = None
        das = []
        ccam = []

        for code_db in codes_db:
            # Charger les preuves
            evidences = []
            for ev_db in code_db.evidences:
                ev_text = ev_db.text
                ev_context = ev_db.context

                if not include_pii:
                    ev_text = self.pii_protector.anonymize_text(ev_text)
                    if ev_context:
                        ev_context = self.pii_protector.anonymize_text(ev_context)

                evidences.append(
                    Evidence(
                        document_id=ev_db.document.document_id,
                        span=Span(start=ev_db.span_start, end=ev_db.span_end),
                        text=ev_text,
                        context=ev_context,
                    )
                )

            reasoning = code_db.reasoning
            if not include_pii:
                reasoning = self.pii_protector.anonymize_text(reasoning)

            code = Code(
                code=code_db.code,
                label=code_db.label,
                type=code_db.type,
                evidence=evidences,
                confidence=code_db.confidence,
                reasoning=reasoning,
                referentiel_version=code_db.referentiel_version,
            )

            # Classer par type
            if code.type == "dp":
                dp = code
            elif code.type == "dr":
                dr = code
            elif code.type == "das":
                das.append(code)
            elif code.type == "ccam":
                ccam.append(code)

        # Récupérer le reasoning global et les versions depuis le premier code
        if codes_db:
            first_code = codes_db[0]
            reasoning_global = "Proposition de codage basée sur les faits cliniques extraits"
            if not include_pii:
                reasoning_global = self.pii_protector.anonymize_text(reasoning_global)

            return CodingProposal(
                stay_id=stay_db.stay_id,
                dp=dp,
                dr=dr,
                das=das,
                ccam=ccam,
                reasoning=reasoning_global,
                model_version=ModelVersion(
                    model_name=first_code.model_name,
                    model_tag="latest",
                    model_digest=first_code.model_digest,
                ),
                prompt_version=first_code.prompt_version,
            )

        return None

    def _load_verification_result(
        self, stay_db: StayDB, include_pii: bool
    ) -> Optional[VerificationResult]:
        """Charge le résultat de vérification avec filtrage DIP optionnel."""
        from pipeline_mco_pmsi.models.coding import DIMError
        from pipeline_mco_pmsi.models.metadata import ModelVersion

        # Récupérer le dernier résultat de vérification
        if not stay_db.verification_results:
            return None

        ver_db = stay_db.verification_results[-1]

        reasoning = ver_db.reasoning
        if not include_pii:
            reasoning = self.pii_protector.anonymize_text(reasoning)

        # Charger les erreurs DIM
        dim_errors = [
            DIMError(**error_data) for error_data in ver_db.dim_errors
        ]

        # Charger les contradictions
        contradictions = ver_db.contradictions
        if not include_pii:
            contradictions = [
                self.pii_protector.anonymize_text(c) for c in contradictions
            ]

        return VerificationResult(
            stay_id=stay_db.stay_id,
            decision=ver_db.decision,
            dim_errors=dim_errors,
            contradictions=contradictions,
            alternatives=[],  # Les alternatives sont stockées séparément
            reasoning=reasoning,
            model_version=ModelVersion(
                model_name=ver_db.model_name,
                model_tag="latest",
                model_digest=ver_db.model_digest,
            ),
            prompt_version=ver_db.prompt_version,
        )

    def _load_validation_issues(
        self, stay_db: StayDB, include_pii: bool
    ) -> List[ValidationIssue]:
        """Charge les problèmes de validation avec filtrage DIP optionnel."""
        issues = []
        for issue_db in stay_db.validation_issues:
            message = issue_db.message
            suggested_action = issue_db.suggested_action

            if not include_pii:
                message = self.pii_protector.anonymize_text(message)
                suggested_action = self.pii_protector.anonymize_text(suggested_action)

            issues.append(
                ValidationIssue(
                    severity=issue_db.severity,
                    category=issue_db.category,
                    message=message,
                    affected_codes=issue_db.affected_codes or [],
                    suggested_action=suggested_action,
                )
            )
        return issues

    def _load_questions(self, stay_db: StayDB, include_pii: bool) -> List[Question]:
        """Charge les questions avec filtrage DIP optionnel."""
        questions = []
        for q_db in stay_db.questions:
            text = q_db.text
            context = q_db.context

            if not include_pii:
                text = self.pii_protector.anonymize_text(text)
                context = self.pii_protector.anonymize_text(context)

            questions.append(
                Question(
                    question_id=q_db.question_id,
                    text=text,
                    priority=q_db.priority,
                    category=q_db.category,
                    context=context,
                    suggested_answers=q_db.suggested_answers or [],
                )
            )
        return questions

    def _load_groupage_result(self, stay_db: StayDB) -> Optional[GroupageResult]:
        """Charge le résultat de groupage."""
        if not stay_db.groupage_results:
            return None

        grp_db = stay_db.groupage_results[-1]

        return GroupageResult(
            stay_id=stay_db.stay_id,
            ghm=grp_db.ghm,
            ghs=grp_db.ghs,
            groupage_errors=[],  # Simplification pour l'export
            ccam_date_errors=grp_db.ccam_date_errors or [],
            groupage_version=grp_db.groupage_version,
            groupage_date=grp_db.groupage_date,
        )

    def _load_tim_corrections(self, stay_db: StayDB) -> List[Dict[str, Any]]:
        """Charge les corrections TIM."""
        corrections = []
        for corr_db in stay_db.codes:
            for tim_corr in corr_db.corrections:
                corrections.append(
                    {
                        "original_code": corr_db.code,
                        "corrected_code": tim_corr.corrected_code,
                        "corrected_label": tim_corr.corrected_label,
                        "user_id": tim_corr.user_id,
                        "comment": tim_corr.comment,
                        "timestamp": tim_corr.timestamp.isoformat(),
                    }
                )
        return corrections

    def _load_audit_records(
        self, stay_db: StayDB, include_pii: bool
    ) -> List[AuditRecord]:
        """Charge les enregistrements d'audit avec filtrage DIP optionnel."""
        from pipeline_mco_pmsi.models.metadata import ReferentielVersion

        records = []
        for audit_db in stay_db.audit_records:
            # Filtrer les données si nécessaire
            data = audit_db.data
            if not include_pii and data:
                data = self._filter_pii_from_dict(data)

            # Reconstruire les versions de référentiels
            referentiels = {}
            for ref_type, ref_data in audit_db.referentiels_versions.items():
                # Convertir import_date de ISO string vers datetime si nécessaire
                if "import_date" in ref_data and isinstance(ref_data["import_date"], str):
                    ref_data["import_date"] = datetime.fromisoformat(ref_data["import_date"])
                referentiels[ref_type] = ReferentielVersion(**ref_data)

            versions = VersionInfo(
                model_name=audit_db.model_name,
                model_tag="latest",
                model_digest=audit_db.model_digest,
                prompt_version=audit_db.prompt_version,
                prompt_hash=audit_db.prompt_hash,
                referentiels=referentiels,
                rules_version=audit_db.rules_version,
                rules_hash=audit_db.rules_hash,
                groupage_version=audit_db.groupage_version,
                inference_params=audit_db.inference_params or {},
            )

            records.append(
                AuditRecord(
                    record_id=audit_db.record_id,
                    stay_id=stay_db.stay_id,  # Utiliser le stay_id string du séjour
                    timestamp=audit_db.timestamp,
                    event_type=audit_db.event_type,
                    actor=audit_db.actor,
                    data=data,
                    versions=versions,
                )
            )
        return records

    # Helper methods for persistence

    def _persist_audit_record(self, audit_record: AuditRecord) -> None:
        """Persiste un enregistrement d'audit dans la base de données."""
        # Récupérer le stay_id interne
        stay_db = (
            self.db_session.query(StayDB)
            .filter(StayDB.stay_id == audit_record.stay_id)
            .first()
        )
        if not stay_db:
            raise ValueError(f"Stay {audit_record.stay_id} not found")

        # Convertir les référentiels en dict avec dates en ISO format
        referentiels_dict = {}
        for ref_type, ref_version in audit_record.versions.referentiels.items():
            ref_dict = ref_version.model_dump()
            # Convertir datetime en ISO string pour JSON
            if "import_date" in ref_dict and isinstance(ref_dict["import_date"], datetime):
                ref_dict["import_date"] = ref_dict["import_date"].isoformat()
            referentiels_dict[ref_type] = ref_dict

        # Créer l'enregistrement DB
        audit_db = AuditRecordDB(
            record_id=audit_record.record_id,
            stay_id=stay_db.id,
            timestamp=audit_record.timestamp,
            event_type=audit_record.event_type,
            actor=audit_record.actor,
            data=audit_record.data,
            model_name=audit_record.versions.model_name,
            model_digest=audit_record.versions.model_digest,
            prompt_version=audit_record.versions.prompt_version,
            prompt_hash=audit_record.versions.prompt_hash,
            rules_version=audit_record.versions.rules_version,
            rules_hash=audit_record.versions.rules_hash,
            groupage_version=audit_record.versions.groupage_version,
            referentiels_versions=referentiels_dict,
            inference_params=audit_record.versions.inference_params,
        )

        self.db_session.add(audit_db)
        self.db_session.commit()

    # Utility methods

    def _generate_record_id(self, stay_id: str, event_type: str) -> str:
        """Génère un identifiant unique pour un enregistrement d'audit."""
        timestamp = datetime.utcnow().isoformat()
        content = f"{stay_id}_{event_type}_{timestamp}"
        hash_digest = hashlib.sha256(content.encode()).hexdigest()
        return f"audit_{hash_digest[:16]}"

    def _filter_pii_from_dict(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Filtre récursivement les DIP d'un dictionnaire."""
        filtered = {}
        for key, value in data.items():
            if isinstance(value, str):
                filtered[key] = self.pii_protector.anonymize_text(value)
            elif isinstance(value, dict):
                filtered[key] = self._filter_pii_from_dict(value)
            elif isinstance(value, list):
                filtered[key] = [
                    self._filter_pii_from_dict(item) if isinstance(item, dict)
                    else self.pii_protector.anonymize_text(item) if isinstance(item, str)
                    else item
                    for item in value
                ]
            else:
                filtered[key] = value
        return filtered

    def _get_default_version_info(self) -> VersionInfo:
        """Retourne des informations de version par défaut."""
        return VersionInfo(
            model_name="unknown",
            model_tag="unknown",
            model_digest="0" * 64,
            prompt_version="unknown",
            prompt_hash="0" * 64,
            referentiels={},
            rules_version="unknown",
            rules_hash="0" * 64,
            groupage_version=None,
            inference_params={},
        )

    # Methods for recording all elements (Subtask 15.2)

    def record_documents(
        self,
        stay_id: str,
        documents: List[ClinicalDocument],
        versions: VersionInfo,
    ) -> AuditRecord:
        """
        Enregistre les documents d'entrée avec métadonnées.

        Args:
            stay_id: Identifiant du séjour
            documents: Liste des documents cliniques
            versions: Informations de version

        Returns:
            AuditRecord créé

        Exigences : 5.1
        """
        record_id = self._generate_record_id(stay_id, "documents_import")

        data = {
            "document_count": len(documents),
            "documents": [
                {
                    "document_id": doc.document_id,
                    "document_type": doc.document_type,
                    "creation_date": doc.creation_date.isoformat(),
                    "author": doc.author,
                    "priority": doc.priority,
                    "content_length": len(doc.content),
                }
                for doc in documents
            ],
        }

        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="import",
            actor="system",
            data=data,
            versions=versions,
        )

        self._persist_audit_record(audit_record)
        return audit_record

    def record_facts(
        self,
        stay_id: str,
        facts: List[ClinicalFact],
        versions: VersionInfo,
    ) -> AuditRecord:
        """
        Enregistre les faits extraits avec preuves.

        Args:
            stay_id: Identifiant du séjour
            facts: Liste des faits cliniques extraits
            versions: Informations de version

        Returns:
            AuditRecord créé

        Exigences : 5.2
        """
        record_id = self._generate_record_id(stay_id, "facts_extraction")

        data = {
            "fact_count": len(facts),
            "facts_by_type": self._count_facts_by_type(facts),
            "facts_by_certainty": self._count_facts_by_certainty(facts),
            "facts": [
                {
                    "fact_id": fact.fact_id,
                    "type": fact.type,
                    "qualifier_certainty": fact.qualifier.certainty,
                    "temporality": fact.temporality,
                    "confidence": fact.confidence,
                    "evidence_document_id": fact.evidence.document_id,
                }
                for fact in facts
            ],
        }

        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="import",
            actor="system",
            data=data,
            versions=versions,
        )

        self._persist_audit_record(audit_record)
        return audit_record

    def record_codes_with_justifications(
        self,
        stay_id: str,
        proposal: CodingProposal,
        versions: VersionInfo,
    ) -> AuditRecord:
        """
        Enregistre les codes proposés avec justifications.

        Args:
            stay_id: Identifiant du séjour
            proposal: Proposition de codage
            versions: Informations de version

        Returns:
            AuditRecord créé

        Exigences : 5.3
        """
        record_id = self._generate_record_id(stay_id, "codes_proposal")

        # Compter les codes par type
        code_counts = {
            "dp": 1 if proposal.dp else 0,
            "dr": 1 if proposal.dr else 0,
            "das": len(proposal.das),
            "ccam": len(proposal.ccam),
        }

        # Extraire les informations de justification
        codes_data = []
        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:
            codes_data.append(
                {
                    "code": code.code,
                    "label": code.label,
                    "type": code.type,
                    "confidence": code.confidence,
                    "evidence_count": len(code.evidence),
                    "referentiel_version": code.referentiel_version,
                    "has_reasoning": len(code.reasoning) > 0,
                }
            )

        data = {
            "code_counts": code_counts,
            "total_codes": sum(code_counts.values()),
            "codes": codes_data,
            "global_reasoning_length": len(proposal.reasoning),
        }

        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="coding",
            actor="codeur",
            data=data,
            versions=versions,
        )

        self._persist_audit_record(audit_record)
        return audit_record

    def record_verification_decision(
        self,
        stay_id: str,
        verification: VerificationResult,
        versions: VersionInfo,
    ) -> AuditRecord:
        """
        Enregistre les décisions du Vérificateur.

        Args:
            stay_id: Identifiant du séjour
            verification: Résultat de vérification
            versions: Informations de version

        Returns:
            AuditRecord créé

        Exigences : 5.5
        """
        record_id = self._generate_record_id(stay_id, "verification")

        data = {
            "decision": verification.decision,
            "dim_errors_count": len(verification.dim_errors),
            "dim_errors": [
                {
                    "error_type": error.error_type,
                    "severity": error.severity,
                    "affected_codes_count": len(error.affected_codes),
                }
                for error in verification.dim_errors
            ],
            "contradictions_count": len(verification.contradictions),
            "alternatives_count": len(verification.alternatives),
            "reasoning_length": len(verification.reasoning),
        }

        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="verification",
            actor="verificateur",
            data=data,
            versions=versions,
        )

        self._persist_audit_record(audit_record)
        return audit_record

    def record_component_versions(
        self,
        stay_id: str,
        versions: VersionInfo,
        component_name: str,
    ) -> AuditRecord:
        """
        Enregistre les versions de tous les composants pour reproductibilité.

        Args:
            stay_id: Identifiant du séjour
            versions: Informations de version complètes
            component_name: Nom du composant (codeur, verificateur, etc.)

        Returns:
            AuditRecord créé

        Exigences : 5.7
        """
        record_id = self._generate_record_id(stay_id, f"version_{component_name}")

        data = {
            "component": component_name,
            "model": {
                "name": versions.model_name,
                "digest": versions.model_digest,
            },
            "prompt": {
                "version": versions.prompt_version,
                "hash": versions.prompt_hash,
            },
            "rules": {
                "version": versions.rules_version,
                "hash": versions.rules_hash,
            },
            "referentiels": {
                ref_type: {
                    "version": ref_ver.version,
                    "file_hash": ref_ver.file_hash,
                    "index_hash": ref_ver.index_hash,
                }
                for ref_type, ref_ver in versions.referentiels.items()
            },
            "groupage_version": versions.groupage_version,
            "inference_params": versions.inference_params,
        }

        audit_record = AuditRecord(
            record_id=record_id,
            stay_id=stay_id,
            timestamp=datetime.utcnow(),
            event_type="import",
            actor="system",
            data=data,
            versions=versions,
        )

        self._persist_audit_record(audit_record)
        return audit_record

    # Helper methods for statistics

    def _count_facts_by_type(self, facts: List[ClinicalFact]) -> Dict[str, int]:
        """Compte les faits par type."""
        counts: Dict[str, int] = {}
        for fact in facts:
            counts[fact.type] = counts.get(fact.type, 0) + 1
        return counts

    def _count_facts_by_certainty(self, facts: List[ClinicalFact]) -> Dict[str, int]:
        """Compte les faits par certitude."""
        counts: Dict[str, int] = {}
        for fact in facts:
            certainty = fact.qualifier.certainty
            counts[certainty] = counts.get(certainty, 0) + 1
        return counts
