"""
Orchestrateur d'extraction avec fallback automatique.

Ce module coordonne l'extraction de faits cliniques en utilisant EDS-NLP
comme méthode principale et un extracteur regex comme fallback.
"""

import logging
import time
from typing import List, Optional

from ..extractors.edsnlp_processor import EDSNLPProcessor
from ..extractors.edsnlp_types import ExtractionResult
from ..extractors import edsnlp_metrics
from ..models.clinical import ClinicalFact, StructuredStay

logger = logging.getLogger(__name__)


class ExtractionOrchestrator:
    """Orchestre l'extraction avec EDS-NLP et fallback."""

    def __init__(
        self,
        edsnlp_processor: Optional[EDSNLPProcessor] = None,
        fallback_extractor: Optional = None,
        max_retries: int = 3,
        cooldown_period: int = 300,  # 5 minutes
    ):
        """
        Initialise l'orchestrateur.

        Args:
            edsnlp_processor: Instance du processeur EDS-NLP
            fallback_extractor: Extracteur regex de fallback
            max_retries: Nombre maximum d'échecs consécutifs avant cooldown
            cooldown_period: Durée du cooldown en secondes après échecs répétés
        """
        self.edsnlp_processor = edsnlp_processor
        self.fallback_extractor = fallback_extractor
        self.max_retries = max_retries
        self.cooldown_period = cooldown_period

        # Suivi des échecs pour le cooldown
        self._failure_count = 0
        self._cooldown_until: Optional[float] = None

        logger.info(
            "extraction_orchestrator_initialized",
            has_edsnlp=edsnlp_processor is not None,
            has_fallback=fallback_extractor is not None,
            max_retries=max_retries,
            cooldown_period=cooldown_period,
        )

    def extract_facts(
        self,
        structured_stay: StructuredStay,
    ) -> ExtractionResult:
        """
        Extrait les faits cliniques en utilisant la meilleure méthode disponible.

        Tente d'abord l'extraction avec EDS-NLP. En cas d'échec, utilise
        l'extracteur regex de fallback.

        Args:
            structured_stay: Séjour avec documents à traiter

        Returns:
            ExtractionResult avec les faits et métadonnées
        """
        start_time = time.time()

        # Vérifier si EDS-NLP doit être utilisé
        if self._should_use_edsnlp():
            # Tenter l'extraction avec EDS-NLP
            facts = self._try_edsnlp_extraction(structured_stay)

            if facts is not None:
                # Succès EDS-NLP
                self._record_success()
                processing_time = time.time() - start_time

                # Enregistrer les métriques
                edsnlp_metrics.track_extraction_success()

                logger.info(
                    "extraction_completed_edsnlp",
                    stay_id=structured_stay.stay_id,
                    facts_count=len(facts),
                    processing_time=processing_time,
                )

                return ExtractionResult(
                    facts=facts,
                    extraction_method="edsnlp",
                    processing_time=processing_time,
                    fallback_used=False,
                )

        # Fallback vers l'extraction regex
        edsnlp_metrics.track_extraction_fallback()
        
        logger.warning(
            "using_fallback_extraction",
            stay_id=structured_stay.stay_id,
            reason="edsnlp_unavailable_or_failed",
        )

        facts = self._fallback_extraction(structured_stay)
        processing_time = time.time() - start_time

        logger.info(
            "extraction_completed_fallback",
            stay_id=structured_stay.stay_id,
            facts_count=len(facts),
            processing_time=processing_time,
        )

        return ExtractionResult(
            facts=facts,
            extraction_method="regex",
            processing_time=processing_time,
            fallback_used=True,
        )

    def _should_use_edsnlp(self) -> bool:
        """
        Vérifie si EDS-NLP doit être tenté.

        Returns:
            True si EDS-NLP est disponible et pas en cooldown
        """
        # Vérifier si EDS-NLP est disponible
        if self.edsnlp_processor is None:
            logger.debug("edsnlp_not_available", reason="processor_not_initialized")
            return False

        # Vérifier le cooldown
        if self._cooldown_until is not None:
            current_time = time.time()
            if current_time < self._cooldown_until:
                remaining = self._cooldown_until - current_time
                logger.debug(
                    "edsnlp_in_cooldown",
                    remaining_seconds=remaining,
                )
                return False
            else:
                # Cooldown terminé, réinitialiser
                logger.info("cooldown_expired", failure_count=self._failure_count)
                self._cooldown_until = None
                self._failure_count = 0

        return True

    def _try_edsnlp_extraction(
        self,
        structured_stay: StructuredStay,
    ) -> Optional[List[ClinicalFact]]:
        """
        Tente l'extraction avec EDS-NLP.

        Args:
            structured_stay: Séjour à traiter

        Returns:
            Liste de ClinicalFact si succès, None si échec
        """
        try:
            from ..extractors.clinical_term_normalizer import ClinicalTermNormalizer
            from ..models.clinical import ClinicalFact, Evidence, Span
            import uuid

            # Créer le normaliseur de termes
            normalizer = ClinicalTermNormalizer()

            # Traiter chaque document du séjour
            all_facts = []

            for document in structured_stay.documents:
                # Traiter le document avec EDS-NLP
                result = self.edsnlp_processor.process_document(
                    text=document.content,
                    document_id=document.document_id,
                )

                if not result.success:
                    logger.warning(
                        "document_processing_failed",
                        document_id=document.document_id,
                        error=result.error,
                    )
                    continue

                # Convertir les entités extraites en ClinicalFact
                for entity in result.entities:
                    # Vérifier si le fait doit être exclu (contexte familial)
                    if self.edsnlp_processor._should_exclude_fact(entity.qualifiers):
                        logger.debug(
                            "fact_excluded",
                            entity_text=entity.text,
                            reason="family_context",
                        )
                        continue

                    # Normaliser le terme
                    normalized = normalizer.normalize(entity.text)

                    # Mapper le qualificateur EDS-NLP vers le modèle
                    qualifier = self.edsnlp_processor._map_qualifier_to_model(entity.qualifiers)

                    # Déterminer la temporalité
                    temporality = "antecedent" if entity.qualifiers.historical else "actuel"

                    # Créer la preuve
                    evidence = Evidence(
                        document_id=document.document_id,
                        span=Span(start=entity.span[0], end=entity.span[1]),
                        text=entity.text,
                        context=self._extract_context(document.content, entity.span[0], entity.span[1]),
                    )

                    # Calculer la confiance finale
                    from ..extractors.edsnlp_processor import calculate_confidence
                    confidence = calculate_confidence(entity, entity.qualifiers)

                    # Mapper le type d'entité vers le type de fait
                    fact_type = self._map_entity_type_to_fact_type(entity.entity_type)

                    # Créer le fait clinique
                    fact = ClinicalFact(
                        fact_id=str(uuid.uuid4()),
                        type=fact_type,
                        text=entity.text,
                        qualifier=qualifier,
                        temporality=temporality,
                        evidence=evidence,
                        confidence=confidence,
                        entity_type=entity.entity_type,
                        normalized_text=normalized.normalized,
                        extraction_method="edsnlp",
                        edsnlp_confidence=entity.confidence,
                    )
                    all_facts.append(fact)

            return all_facts

        except Exception as e:
            # Enregistrer l'échec
            edsnlp_metrics.track_extraction_failure()
            self._record_failure(e)
            logger.error(
                "edsnlp_extraction_failed",
                stay_id=structured_stay.stay_id,
                error=str(e),
                failure_count=self._failure_count,
                exc_info=True,
            )
            return None

    def _fallback_extraction(
        self,
        structured_stay: StructuredStay,
    ) -> List[ClinicalFact]:
        """
        Effectue l'extraction avec la méthode regex de fallback.

        Args:
            structured_stay: Séjour à traiter

        Returns:
            Liste de ClinicalFact
        """
        if self.fallback_extractor is None:
            logger.error("fallback_extractor_not_available")
            return []

        try:
            # Utiliser l'extracteur de fallback
            facts = self.fallback_extractor.extract_facts(structured_stay)
            return facts

        except Exception as e:
            logger.error(
                "fallback_extraction_failed",
                stay_id=structured_stay.stay_id,
                error=str(e),
                exc_info=True,
            )
            return []

    def _record_failure(self, error: Exception) -> None:
        """
        Enregistre un échec EDS-NLP pour la logique de cooldown.

        Args:
            error: Exception qui a causé l'échec
        """
        self._failure_count += 1

        logger.warning(
            "edsnlp_failure_recorded",
            failure_count=self._failure_count,
            max_retries=self.max_retries,
            error=str(error),
        )

        # Activer le cooldown si le seuil est atteint
        if self._failure_count >= self.max_retries:
            self._cooldown_until = time.time() + self.cooldown_period
            logger.warning(
                "cooldown_activated",
                failure_count=self._failure_count,
                cooldown_period=self.cooldown_period,
                cooldown_until=self._cooldown_until,
            )

    def _record_success(self) -> None:
        """Enregistre un succès EDS-NLP pour réinitialiser le compteur d'échecs."""
        if self._failure_count > 0:
            logger.info(
                "edsnlp_success_after_failures",
                previous_failure_count=self._failure_count,
            )
            self._failure_count = 0
            self._cooldown_until = None

    def _map_entity_type_to_fact_type(self, entity_type: str) -> str:
        """
        Mappe un type d'entité EDS-NLP vers un type de fait clinique.
        
        Args:
            entity_type: Type d'entité EDS-NLP
            
        Returns:
            Type de fait clinique
        """
        mapping = {
            "diagnostic": "diagnostic",
            "medication": "traitement",
            "procedure": "acte",
            "date": "examen",
            "measurement": "examen",
        }
        return mapping.get(entity_type, "diagnostic")
    
    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
