"""
Clinical Facts Extractor pour l'extraction de faits cliniques structurés.

Ce module implémente l'extraction de faits cliniques (diagnostics, actes, examens,
traitements) avec détection de qualificateurs (négation, suspicion, temporalité)
et association de preuves textuelles.
"""

import re
import uuid
from typing import List, Dict, Tuple, Optional

from ..models.clinical import (
    ClinicalFact,
    Evidence,
    Qualifier,
    Section,
    Span,
    StructuredStay,
)


# Marqueurs linguistiques pour la détection de qualificateurs
NEGATION_MARKERS = [
    r"\bpas\s+de\b",
    r"\babsence\s+de\b",
    r"\bsans\b",
    r"\baucun(?:e)?\b",
    r"\bni\b",
    r"\bn['\'](?:a|ont|avait|avaient)\s+pas\b",
    r"\bexclu(?:e|s)?\b",
    r"\binfirm(?:é|ée|és|ées)\b",
    r"\bnon\s+(?:retrouvé|observé|constaté|mis en évidence)\b",
]

SUSPICION_MARKERS = [
    r"\bpossible(?:ment)?\b",
    r"\bsuspect(?:é|ée|és|ées)?\b",
    r"\bprobable(?:ment)?\b",
    r"\bà\s+confirmer\b",
    r"\bà\s+éliminer\b",
    r"\bà\s+évoquer\b",
    r"\bévocateur\b",
    r"\bcompatible\s+avec\b",
    r"\ben\s+faveur\s+de\b",
    r"\bdouteux\b",
    r"\bincertain\b",
    r"\bhypothèse\b",
]

TEMPORALITY_MARKERS = {
    "antecedent": [
        r"\bantécédent(?:s)?\b",
        r"\bancien(?:ne|s|nes)?\b",
        r"\bhistoire\s+de\b",
        r"\bconnu(?:e|s|es)?\s+pour\b",
        r"\bsuivi(?:e)?\s+pour\b",
        r"\btraité(?:e)?\s+pour\b",
        r"\bopéré(?:e)?\s+pour\b",
        r"\bpassé\b",
        r"\bantérieur(?:e|s|es)?\b",
    ],
    "chronique": [
        r"\bchronique\b",
        r"\bpersistant(?:e|s|es)?\b",
        r"\bau\s+long\s+cours\b",
        r"\bde\s+longue\s+date\b",
        r"\brécidivant(?:e|s|es)?\b",
    ],
}

# Patterns pour détecter les types de faits cliniques
FACT_TYPE_PATTERNS = {
    "diagnostic": [
        r"\bdiagnostic(?:s)?\s*:\s*([^\n\.]+)",
        r"\bconclusion\s*:\s*([^\n\.]+)",
        r"\bimpression\s*:\s*([^\n\.]+)",
        r"\bretenu\s*:\s*([^\n\.]+)",
        # Patterns pour détecter des diagnostics dans le texte libre
        r"(?:présence|mise en évidence|constatation)\s+(?:d['\'])?une?\s+([^\n\.]+)",
        r"(?:souffre|atteint|porteur)\s+(?:d['\'])?une?\s+([^\n\.]+)",
        # Patterns pour antécédents
        r"(?:antécédent|histoire)\s+(?:d['\'])?(?:une?\s+)?([^\n\.]+)",
        r"(?:connu|suivi|traité|opéré)(?:e)?\s+pour\s+([^\n\.]+)",
    ],
    "acte": [
        r"\bacte(?:s)?\s*:\s*([^\n\.]+)",
        r"\bintervention(?:s)?\s*:\s*([^\n\.]+)",
        r"\bprocédure(?:s)?\s*:\s*([^\n\.]+)",
        r"\bchirurgie\s*:\s*([^\n\.]+)",
        r"(?:réalisation|pratique)\s+(?:d['\'])?une?\s+([^\n\.]+)",
        r"(?:a été|ont été)\s+(?:réalisé|pratiqué|effectué)(?:e|s)?\s*:\s*([^\n\.]+)",
    ],
    "examen": [
        r"\bexamen(?:s)?\s*:\s*([^\n\.]+)",
        r"\bbilan\s*:\s*([^\n\.]+)",
        r"\bimagerie\s*:\s*([^\n\.]+)",
        r"\bbiologie\s*:\s*([^\n\.]+)",
        r"(?:scanner|IRM|échographie|radiographie|TDM)\s*:\s*([^\n\.]+)",
        r"(?:montre|révèle|objective)\s+([^\n\.]+)",
    ],
    "traitement": [
        r"\btraitement(?:s)?\s*:\s*([^\n\.]+)",
        r"\bthérapeutique\s*:\s*([^\n\.]+)",
        r"\bprescription(?:s)?\s*:\s*([^\n\.]+)",
        r"\bmédication(?:s)?\s*:\s*([^\n\.]+)",
        r"(?:prescription|administration|instauration)\s+(?:d['\'])?une?\s+([^\n\.]+)",
        r"(?:mis|mise)\s+sous\s+([^\n\.]+)",
    ],
}


class ClinicalFactsExtractor:
    """
    Extracteur de faits cliniques structurés.
    
    Responsabilités:
    - Extraction de faits cliniques (diagnostics, actes, examens, traitements)
    - Détection de qualificateurs (négation, suspicion, temporalité)
    - Association de preuves textuelles avec positions exactes
    """

    def __init__(self, llm_client=None, enable_edsnlp: bool = False, edsnlp_config=None):
        """
        Initialise l'extracteur de faits cliniques.
        
        Args:
            llm_client: Client LLM (OllamaClient) pour extraction assistée par IA (optionnel)
            enable_edsnlp: Si True, utilise EDS-NLP pour l'extraction (défaut: False)
            edsnlp_config: Configuration EDS-NLP (optionnel)
        """
        self.llm_client = llm_client
        self.enable_edsnlp = enable_edsnlp
        
        # Initialiser l'orchestrateur EDS-NLP si activé
        self.orchestrator = None
        if enable_edsnlp:
            try:
                from ..extractors.edsnlp_processor import EDSNLPProcessor
                from ..extractors.edsnlp_config import EDSNLPConfig
                from ..extractors.extraction_orchestrator import ExtractionOrchestrator
                
                # Créer la configuration EDS-NLP
                if edsnlp_config is None:
                    edsnlp_config = EDSNLPConfig()
                
                # Créer le processeur EDS-NLP
                edsnlp_processor = EDSNLPProcessor(config=edsnlp_config)
                
                # Créer l'orchestrateur avec self comme fallback
                self.orchestrator = ExtractionOrchestrator(
                    edsnlp_processor=edsnlp_processor,
                    fallback_extractor=self,
                )
                
                import logging
                logger = logging.getLogger(__name__)
                logger.info("EDS-NLP integration enabled")
                
            except Exception as e:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Failed to initialize EDS-NLP, falling back to regex: {e}")
                self.enable_edsnlp = False
        
        # Compiler les patterns pour performance
        self._negation_patterns = [re.compile(p, re.IGNORECASE) for p in NEGATION_MARKERS]
        self._suspicion_patterns = [re.compile(p, re.IGNORECASE) for p in SUSPICION_MARKERS]
        self._temporality_patterns = {
            temp_type: [re.compile(p, re.IGNORECASE) for p in patterns]
            for temp_type, patterns in TEMPORALITY_MARKERS.items()
        }
        self._fact_type_patterns = {
            fact_type: [re.compile(p, re.IGNORECASE) for p in patterns]
            for fact_type, patterns in FACT_TYPE_PATTERNS.items()
        }

    def extract_facts(self, structured_stay: StructuredStay) -> List[ClinicalFact]:
        """
        Extrait les faits cliniques structurés d'un séjour.
        
        Args:
            structured_stay: Séjour structuré avec sections segmentées
            
        Returns:
            Liste de faits cliniques avec preuves, qualificateurs et temporalité
            
        Notes:
            - Si EDS-NLP est activé, utilise l'orchestrateur pour l'extraction
            - Sinon, analyse chaque section pour extraire les faits
            - Détecte automatiquement les qualificateurs dans le contexte
            - Associe chaque fait avec une preuve textuelle précise
            - Utilise le LLM si disponible pour améliorer l'extraction
        """
        # Si EDS-NLP est activé, déléguer à l'orchestrateur
        if self.enable_edsnlp and self.orchestrator is not None:
            try:
                result = self.orchestrator.extract_facts(structured_stay)
                return result.facts
            except Exception as e:
                import logging
                logger = logging.getLogger(__name__)
                logger.error(f"EDS-NLP extraction failed, using fallback: {e}")
                # Continuer avec l'extraction regex ci-dessous
        
        # Extraction regex standard (fallback ou mode par défaut)
        facts = []

        # Extraire les faits de chaque section
        for section in structured_stay.sections:
            # Trouver le document source de cette section
            document_id = section.section_id.split("_section_")[0]
            
            # Extraire les faits selon le type de section
            if self.llm_client:
                # Utiliser l'extraction assistée par LLM
                section_facts = self._extract_facts_with_llm(section, document_id)
            else:
                # Utiliser l'extraction basée sur des règles
                section_facts = self._extract_facts_from_section(section, document_id)
            
            facts.extend(section_facts)

        return facts

    def _extract_facts_with_llm(
        self, section: Section, document_id: str
    ) -> List[ClinicalFact]:
        """
        Extrait les faits d'une section en utilisant le LLM.
        
        Args:
            section: Section à analyser
            document_id: ID du document source
            
        Returns:
            Liste de faits extraits de la section
        """
        import logging
        import json
        
        logger = logging.getLogger(__name__)
        
        # Préparer le prompt pour le LLM
        system_prompt = """Tu es un expert en codage médical PMSI. Ta tâche est d'extraire les faits cliniques structurés d'un document médical.

Pour chaque fait clinique, identifie:
- type: "diagnostic", "acte", "examen", "traitement", ou "complication"
- text: le texte exact du fait
- certainty: "affirmé", "nié", ou "suspecté"
- temporality: "actuel", "antecedent", ou "chronique"
- evidence_text: l'extrait textuel qui prouve ce fait

Réponds UNIQUEMENT avec un JSON valide contenant une liste de faits."""

        user_prompt = f"""Analyse ce document médical et extrais tous les faits cliniques:

Section: {section.section_type}
Contenu:
{section.content}

Format de réponse JSON:
{{
  "facts": [
    {{
      "type": "diagnostic",
      "text": "cancer du sein",
      "certainty": "affirmé",
      "temporality": "actuel",
      "evidence_text": "carcinome mammaire confirmé"
    }}
  ]
}}"""

        try:
            # Appeler le LLM
            response = self.llm_client.generate_json(
                prompt=user_prompt,
                system=system_prompt,
                temperature=0.1
            )
            
            # Parser la réponse
            llm_facts = response.get("facts", [])
            logger.info(f"LLM a extrait {len(llm_facts)} faits de la section {section.section_type}")
            
            # Convertir les faits LLM en objets ClinicalFact
            facts = []
            for llm_fact in llm_facts:
                # Trouver la position du texte de preuve dans le contenu
                evidence_text = llm_fact.get("evidence_text", llm_fact.get("text", ""))
                evidence_pos = section.content.lower().find(evidence_text.lower())
                
                if evidence_pos == -1:
                    # Si le texte exact n'est pas trouvé, utiliser le début de la section
                    evidence_pos = 0
                    evidence_end = min(len(evidence_text), len(section.content))
                    evidence_text = section.content[evidence_pos:evidence_end]
                else:
                    evidence_end = evidence_pos + len(evidence_text)
                
                # Calculer la position absolue dans le document
                abs_start = section.span.start + evidence_pos
                abs_end = section.span.start + evidence_end
                
                # Créer la preuve
                evidence = Evidence(
                    document_id=document_id,
                    span=Span(start=abs_start, end=abs_end),
                    text=evidence_text,
                    context=self._extract_context(section.content, evidence_pos, evidence_end),
                )
                
                # Créer le qualificateur
                certainty = llm_fact.get("certainty", "affirmé")
                qualifier = Qualifier(
                    certainty=certainty,
                    markers=[],
                    confidence=0.9 if certainty == "affirmé" else 0.6,
                )
                
                # Temporalité
                temporality = llm_fact.get("temporality", "actuel")
                
                # Calculer la confiance
                confidence = self._calculate_confidence(qualifier, temporality)
                
                # Créer le fait clinique
                fact = ClinicalFact(
                    fact_id=str(uuid.uuid4()),
                    type=llm_fact.get("type", "diagnostic"),
                    text=llm_fact.get("text", ""),
                    qualifier=qualifier,
                    temporality=temporality,
                    evidence=evidence,
                    confidence=confidence,
                )
                facts.append(fact)
            
            return facts
            
        except Exception as e:
            logger.error(f"Erreur lors de l'extraction LLM: {e}")
            # Fallback sur l'extraction basée sur des règles
            logger.info("Fallback sur l'extraction basée sur des règles")
            return self._extract_facts_from_section(section, document_id)

    def _extract_facts_from_section(
        self, section: Section, document_id: str
    ) -> List[ClinicalFact]:
        """
        Extrait les faits d'une section spécifique.
        
        Args:
            section: Section à analyser
            document_id: ID du document source
            
        Returns:
            Liste de faits extraits de la section
        """
        facts = []
        content = section.content

        # Essayer d'extraire chaque type de fait
        for fact_type, patterns in self._fact_type_patterns.items():
            for pattern in patterns:
                matches = pattern.finditer(content)
                for match in matches:
                    # Extraire le texte du fait
                    fact_text = match.group(1).strip() if match.groups() else match.group(0).strip()
                    
                    if not fact_text or len(fact_text) < 3:
                        continue

                    # Calculer la position absolue dans le document
                    match_start = section.span.start + match.start()
                    match_end = section.span.start + match.end()

                    # Créer la preuve
                    evidence = Evidence(
                        document_id=document_id,
                        span=Span(start=match_start, end=match_end),
                        text=fact_text,
                        context=self._extract_context(content, match.start(), match.end()),
                    )

                    # Détecter les qualificateurs dans le contexte élargi
                    # Utiliser le début du match complet (pas du groupe) pour capturer les marqueurs avant
                    context_start_pos = match.start()  # Début du match complet
                    context_window = self._get_context_window(content, context_start_pos, match.end(), window_size=150)
                    qualifier = self.detect_qualifiers(context_window, fact_text)

                    # Détecter la temporalité
                    temporality = self._detect_temporality(context_window)

                    # Calculer la confiance basée sur les qualificateurs
                    confidence = self._calculate_confidence(qualifier, temporality)

                    # Créer le fait clinique
                    fact = ClinicalFact(
                        fact_id=str(uuid.uuid4()),
                        type=fact_type,
                        text=fact_text,
                        qualifier=qualifier,
                        temporality=temporality,
                        evidence=evidence,
                        confidence=confidence,
                    )
                    facts.append(fact)

        return facts

    def detect_qualifiers(self, text: str, fact_text: str) -> Qualifier:
        """
        Détecte les qualificateurs (affirmé/nié/suspecté) dans un texte.
        
        Args:
            text: Texte à analyser (contexte du fait)
            fact_text: Texte du fait lui-même
            
        Returns:
            Qualifier avec certitude, marqueurs détectés et confiance
            
        Notes:
            - Détecte la négation ("pas de", "absence de", "sans")
            - Détecte la suspicion ("possible", "suspecté", "probable")
            - Réduit la confiance en présence de marqueurs
        """
        detected_markers = []
        certainty = "affirmé"
        confidence = 1.0

        # Vérifier la négation (priorité la plus haute)
        for pattern in self._negation_patterns:
            matches = pattern.finditer(text)
            for match in matches:
                # Vérifier que le marqueur est proche du fait (dans les 50 caractères)
                if self._is_marker_relevant(text, match.start(), fact_text):
                    detected_markers.append(match.group(0))
                    certainty = "nié"
                    confidence = 0.3  # Confiance réduite pour les faits niés

        # Si pas de négation, vérifier la suspicion
        if certainty != "nié":
            for pattern in self._suspicion_patterns:
                matches = pattern.finditer(text)
                for match in matches:
                    if self._is_marker_relevant(text, match.start(), fact_text):
                        detected_markers.append(match.group(0))
                        certainty = "suspecté"
                        confidence = 0.6  # Confiance réduite pour les faits suspectés

        return Qualifier(
            certainty=certainty,
            markers=detected_markers,
            confidence=confidence,
        )

    def _detect_temporality(self, text: str) -> str:
        """
        Détecte la temporalité d'un fait (actuel/antécédent/chronique).
        
        Args:
            text: Texte à analyser
            
        Returns:
            Temporalité détectée ("actuel", "antecedent", ou "chronique")
        """
        # Vérifier les antécédents
        for pattern in self._temporality_patterns["antecedent"]:
            if pattern.search(text):
                return "antecedent"

        # Vérifier les conditions chroniques
        for pattern in self._temporality_patterns["chronique"]:
            if pattern.search(text):
                return "chronique"

        # Par défaut, considérer comme actuel
        return "actuel"

    def _is_marker_relevant(self, text: str, marker_pos: int, fact_text: str) -> bool:
        """
        Vérifie si un marqueur est pertinent pour un fait donné.
        
        Args:
            text: Texte complet
            marker_pos: Position du marqueur dans le texte
            fact_text: Texte du fait
            
        Returns:
            True si le marqueur est proche du fait (dans les 50 caractères avant ou après)
        """
        # Trouver la position du fait dans le texte (case-insensitive)
        text_lower = text.lower()
        fact_lower = fact_text.lower()
        fact_pos = text_lower.find(fact_lower)
        if fact_pos == -1:
            # Si le fait exact n'est pas trouvé, chercher les premiers mots
            words = fact_lower.split()
            if words:
                fact_pos = text_lower.find(words[0])
        
        if fact_pos == -1:
            # Si toujours pas trouvé, considérer le marqueur comme pertinent
            # (il est dans le contexte)
            return True

        # Calculer la distance entre le marqueur et le fait
        # Le marqueur peut être avant, dans, ou après le fait
        fact_end = fact_pos + len(fact_lower)
        
        if marker_pos >= fact_pos and marker_pos < fact_end:
            # Le marqueur est à l'intérieur du fait - toujours pertinent
            return True
        elif marker_pos < fact_pos:
            # Marqueur avant le fait
            distance = fact_pos - marker_pos
        else:
            # Marqueur après le fait
            distance = marker_pos - fact_end
        
        # Le marqueur est pertinent s'il est proche (max 50 caractères)
        return 0 <= distance <= 50

    def _get_context_window(self, text: str, start: int, end: int, window_size: int = 100) -> str:
        """
        Extrait une fenêtre de contexte autour d'une position.
        
        Args:
            text: Texte complet
            start: Position de début
            end: Position de fin
            window_size: Taille de la fenêtre de contexte (caractères avant et après)
            
        Returns:
            Texte de contexte
        """
        context_start = max(0, start - window_size)
        context_end = min(len(text), end + window_size)
        return text[context_start:context_end]

    def _extract_context(self, text: str, start: int, end: int, context_size: int = 50) -> str:
        """
        Extrait le contexte élargi autour d'un extrait.
        
        Args:
            text: Texte complet
            start: Position de début de l'extrait
            end: Position de fin de l'extrait
            context_size: Nombre de caractères avant et après
            
        Returns:
            Contexte élargi (±context_size caractères)
        """
        context_start = max(0, start - context_size)
        context_end = min(len(text), end + context_size)
        
        context = text[context_start:context_end]
        
        # Ajouter des ellipses si le contexte est tronqué
        if context_start > 0:
            context = "..." + context
        if context_end < len(text):
            context = context + "..."
            
        return context

    def _calculate_confidence(self, qualifier: Qualifier, temporality: str) -> float:
        """
        Calcule le score de confiance d'un fait basé sur ses qualificateurs.
        
        Args:
            qualifier: Qualificateur du fait
            temporality: Temporalité du fait
            
        Returns:
            Score de confiance entre 0.0 et 1.0
            
        Notes:
            - Faits affirmés actuels: confiance élevée (0.9)
            - Faits suspectés: confiance réduite (0.6)
            - Faits niés: confiance très réduite (0.3)
            - Antécédents: confiance légèrement réduite (0.8)
        """
        # Commencer avec la confiance du qualificateur
        confidence = qualifier.confidence

        # Réduire légèrement pour les antécédents
        if temporality == "antecedent":
            confidence *= 0.9

        # Réduire légèrement pour les conditions chroniques
        elif temporality == "chronique":
            confidence *= 0.95

        return min(1.0, max(0.0, confidence))
