"""
Normalisation des termes cliniques pour améliorer la recherche RAG.

Ce module fournit des fonctionnalités de normalisation des termes médicaux
pour améliorer la qualité de la recherche par similarité dans le système RAG.
"""

import json
import logging
import unicodedata
from pathlib import Path
from typing import Dict, Optional

from ..extractors.edsnlp_exceptions import NormalizationError
from ..extractors.edsnlp_types import NormalizedTerm

logger = logging.getLogger(__name__)


class ClinicalTermNormalizer:
    """Normalise les termes cliniques vers des formes canoniques."""

    def __init__(
        self,
        abbreviations_path: Optional[Path] = None,
        custom_rules: Optional[Dict[str, str]] = None,
    ):
        """
        Initialise le normaliseur avec un dictionnaire d'abréviations.

        Args:
            abbreviations_path: Chemin vers le fichier JSON contenant les abréviations
            custom_rules: Règles de normalisation supplémentaires

        Raises:
            NormalizationError: Si le chargement des abréviations échoue
        """
        self.custom_rules = custom_rules or {}
        self.abbreviations: Dict[str, str] = {}

        # Charger les abréviations depuis le fichier
        if abbreviations_path is None:
            # Chemin par défaut
            abbreviations_path = (
                Path(__file__).parent.parent.parent / "config" / "medical_abbreviations.json"
            )

        try:
            if abbreviations_path.exists():
                with open(abbreviations_path, "r", encoding="utf-8") as f:
                    data = json.load(f)
                    self.abbreviations = data.get("abbreviations", {})
                logger.info(
                    "abbreviations_loaded",
                    count=len(self.abbreviations),
                    path=str(abbreviations_path),
                )
            else:
                logger.warning(
                    "abbreviations_file_not_found",
                    path=str(abbreviations_path),
                )
        except Exception as e:
            raise NormalizationError(
                message=f"Failed to load abbreviations from {abbreviations_path}",
                term="",
                operation="load_abbreviations",
                details={"error": str(e), "path": str(abbreviations_path)},
            ) from e

    def normalize(
        self,
        term: str,
        preserve_original: bool = True,
    ) -> NormalizedTerm:
        """
        Normalise un terme clinique.

        Pipeline de normalisation:
        1. Conversion en minuscules
        2. Suppression des accents
        3. Expansion des abréviations
        4. Normalisation des espaces

        Args:
            term: Terme clinique original
            preserve_original: Si True, conserve le terme original

        Returns:
            NormalizedTerm avec les formes originale et normalisée

        Raises:
            NormalizationError: Si la normalisation échoue
        """
        if not term or not term.strip():
            return NormalizedTerm(
                original=term,
                normalized=term,
            )

        try:
            # Pipeline de normalisation
            normalized = term
            normalized = self.to_lowercase(normalized)
            normalized = self.remove_accents(normalized)
            normalized = self.expand_abbreviations(normalized)
            normalized = self._normalize_whitespace(normalized)

            return NormalizedTerm(
                original=term if preserve_original else normalized,
                normalized=normalized,
            )

        except Exception as e:
            raise NormalizationError(
                message=f"Failed to normalize term: {term}",
                term=term,
                operation="normalize",
                details={"error": str(e)},
            ) from e

    def to_lowercase(self, term: str) -> str:
        """
        Convertit le terme en minuscules.

        Args:
            term: Terme à convertir

        Returns:
            Terme en minuscules
        """
        return term.lower()

    def remove_accents(self, term: str) -> str:
        """
        Supprime les accents et diacritiques.

        Utilise la décomposition Unicode NFD pour séparer les caractères
        de base des marques diacritiques, puis filtre ces marques.

        Args:
            term: Terme avec accents

        Returns:
            Terme sans accents (é→e, à→a, ç→c, etc.)
        """
        # Décomposition NFD: sépare les caractères de base des diacritiques
        nfd = unicodedata.normalize("NFD", term)
        
        # Filtre les marques diacritiques (catégorie Mn)
        without_accents = "".join(
            char for char in nfd if unicodedata.category(char) != "Mn"
        )
        
        # Recomposition NFC pour normaliser
        return unicodedata.normalize("NFC", without_accents)

    def expand_abbreviations(self, term: str) -> str:
        """
        Expanse les abréviations médicales connues.

        Recherche le terme complet dans le dictionnaire d'abréviations.
        Si trouvé, retourne la forme expansée, sinon retourne le terme original.

        Args:
            term: Terme potentiellement abrégé

        Returns:
            Terme expansé si l'abréviation est connue, sinon terme original
        """
        # Recherche exacte dans le dictionnaire
        term_lower = term.lower()
        if term_lower in self.abbreviations:
            expanded = self.abbreviations[term_lower]
            logger.debug(
                "abbreviation_expanded",
                original=term,
                expanded=expanded,
            )
            return expanded

        # Recherche dans les règles personnalisées
        if term_lower in self.custom_rules:
            return self.custom_rules[term_lower]

        # Pas d'abréviation trouvée, retourne le terme original
        return term

    def _normalize_whitespace(self, term: str) -> str:
        """
        Normalise les espaces blancs.

        Remplace les séquences d'espaces multiples par un seul espace
        et supprime les espaces en début/fin.

        Args:
            term: Terme avec espaces potentiellement irréguliers

        Returns:
            Terme avec espaces normalisés
        """
        return " ".join(term.split())
