"""
Validateur de groupage PMSI utilisant la Fonction de Groupage (FG) ATIH.

Ce module implémente la validation tarifaire des codes CIM-10/CCAM via la fonction
de groupage ATIH, qui transforme les codes en GHM/GHS et détecte les erreurs bloquantes.

Pour le POC, une implémentation mock/stub est fournie car la bibliothèque ATIH réelle
peut ne pas être disponible. En production, cette implémentation serait remplacée par
l'intégration de la bibliothèque officielle ATIH.
"""

import hashlib
import uuid
from datetime import datetime
from typing import Dict, List, Optional

from pydantic import BaseModel, Field

from pipeline_mco_pmsi.models.coding import Code, CodingProposal
from pipeline_mco_pmsi.models.metadata import StayMetadata
from pipeline_mco_pmsi.models.validation import GroupageResult, ValidationIssue


class GroupageValidator:
    """
    Validateur de groupage PMSI.

    Ce composant valide les codes proposés via la fonction de groupage ATIH,
    transforme les codes CIM-10/CCAM en GHM/GHS, et détecte les erreurs bloquantes.

    Responsabilités:
    - Intégrer la Fonction de Groupage (FG) ATIH
    - Transformer CIM-10/CCAM → GHM/GHS
    - Vérifier les dates de réalisation CCAM (erreur bloquante 2026)
    - Vérifier la version FG correspond à l'année de codage
    - Enregistrer la version FG dans l'audit
    """

    def __init__(self, groupage_version: str = "2026"):
        """
        Initialise le validateur de groupage.

        Args:
            groupage_version: Version de la fonction de groupage (ex: "2026")
        """
        self.groupage_version = groupage_version
        self._fg_library = self._initialize_fg_library()

    def _initialize_fg_library(self) -> Optional[object]:
        """
        Initialise la bibliothèque de fonction de groupage ATIH.

        Pour le POC, retourne None (implémentation mock).
        En production, ceci chargerait la bibliothèque ATIH réelle.

        Returns:
            Instance de la bibliothèque FG ou None pour le mock
        """
        # TODO: En production, charger la bibliothèque ATIH réelle
        # from atih_fg import FonctionGroupage
        # return FonctionGroupage(version=self.groupage_version)
        return None

    def validate_groupage(
        self,
        codes: CodingProposal,
        stay_metadata: StayMetadata,
    ) -> GroupageResult:
        """
        Valide les codes via la fonction de groupage.

        Transforme les codes CIM-10/CCAM en GHM/GHS et détecte les erreurs bloquantes.

        Args:
            codes: Proposition de codage complète
            stay_metadata: Métadonnées du séjour

        Returns:
            GroupageResult avec GHM/GHS et erreurs détectées

        Raises:
            ValueError: Si la version FG ne correspond pas à l'année de codage
        """
        # Vérifier que la version FG correspond à l'année de codage
        self._verify_fg_version(stay_metadata)

        # Vérifier les dates de réalisation CCAM (erreur bloquante 2026)
        ccam_date_errors = self.check_ccam_dates(codes.ccam)

        # Collecter tous les codes pour le groupage
        all_codes = self._collect_codes_for_groupage(codes)

        # Effectuer le groupage (mock pour POC)
        ghm, ghs, groupage_errors = self._perform_groupage(
            all_codes, stay_metadata, ccam_date_errors
        )

        return GroupageResult(
            stay_id=codes.stay_id,
            ghm=ghm,
            ghs=ghs,
            groupage_errors=groupage_errors,
            ccam_date_errors=ccam_date_errors,
            groupage_version=self.groupage_version,
            groupage_date=datetime.now(),
        )

    def check_ccam_dates(self, ccam_codes: List[Code]) -> List[str]:
        """
        Vérifie la présence des dates de réalisation pour les actes CCAM.

        Depuis 2026, l'absence de date de réalisation est une erreur bloquante.

        Args:
            ccam_codes: Liste des codes CCAM proposés

        Returns:
            Liste des codes CCAM sans date de réalisation
        """
        missing_dates = []

        for code in ccam_codes:
            # Vérifier si le code a une date de réalisation dans ses métadonnées
            # Pour le POC, on simule cette vérification en cherchant une date
            # dans le reasoning ou les preuves
            if not self._has_realization_date(code):
                missing_dates.append(code.code)

        return missing_dates

    def _has_realization_date(self, code: Code) -> bool:
        """
        Vérifie si un code CCAM a une date de réalisation.

        Pour le POC, on simule cette vérification en cherchant des mentions
        de dates dans le reasoning ou les preuves.

        Args:
            code: Code CCAM à vérifier

        Returns:
            True si une date de réalisation est présente, False sinon
        """
        # Rechercher des patterns de dates dans le reasoning
        date_keywords = [
            "date",
            "réalisé le",
            "effectué le",
            "pratiqué le",
            "intervention du",
            "acte du",
        ]

        reasoning_lower = code.reasoning.lower()
        for keyword in date_keywords:
            if keyword in reasoning_lower:
                return True

        # Rechercher dans les preuves
        for evidence in code.evidence:
            evidence_text_lower = evidence.text.lower()
            for keyword in date_keywords:
                if keyword in evidence_text_lower:
                    return True

        # Par défaut, considérer qu'il n'y a pas de date
        return False

    def _verify_fg_version(self, stay_metadata: StayMetadata) -> None:
        """
        Vérifie que la version FG correspond à l'année de codage.

        Args:
            stay_metadata: Métadonnées du séjour

        Raises:
            ValueError: Si la version FG ne correspond pas à l'année
        """
        # Extraire l'année de codage depuis les métadonnées
        coding_year = stay_metadata.admission_date.year

        # Vérifier la correspondance
        if str(coding_year) != self.groupage_version:
            raise ValueError(
                f"Version FG ({self.groupage_version}) ne correspond pas "
                f"à l'année de codage ({coding_year})"
            )

    def _collect_codes_for_groupage(self, codes: CodingProposal) -> Dict[str, List[str]]:
        """
        Collecte tous les codes pour le groupage.

        Args:
            codes: Proposition de codage

        Returns:
            Dictionnaire avec les codes par type (dp, dr, das, ccam)
        """
        result = {
            "dp": [codes.dp.code] if codes.dp else [],
            "dr": [codes.dr.code] if codes.dr else [],
            "das": [code.code for code in codes.das],
            "ccam": [code.code for code in codes.ccam],
        }
        return result

    def _perform_groupage(
        self,
        all_codes: Dict[str, List[str]],
        stay_metadata: StayMetadata,
        ccam_date_errors: List[str],
    ) -> tuple[Optional[str], Optional[str], List[ValidationIssue]]:
        """
        Effectue le groupage des codes en GHM/GHS.

        Pour le POC, cette méthode fournit une implémentation mock.
        En production, elle appellerait la bibliothèque ATIH réelle.

        Args:
            all_codes: Codes collectés par type
            stay_metadata: Métadonnées du séjour
            ccam_date_errors: Codes CCAM sans date

        Returns:
            Tuple (GHM, GHS, erreurs de groupage)
        """
        groupage_errors = []

        # Si des dates CCAM manquent, c'est une erreur bloquante
        if ccam_date_errors:
            for ccam_code in ccam_date_errors:
                error = ValidationIssue(
                    issue_id=str(uuid.uuid4()),
                    severity="bloquant",
                    category="groupage",
                    message=f"Date de réalisation manquante pour l'acte CCAM {ccam_code} "
                    f"(erreur bloquante 2026)",
                    affected_codes=[ccam_code],
                    suggested_action="Ajouter la date de réalisation de l'acte CCAM",
                )
                groupage_errors.append(error)

        # Vérifier qu'il y a au moins un DP
        if not all_codes["dp"]:
            error = ValidationIssue(
                issue_id=str(uuid.uuid4()),
                severity="bloquant",
                category="groupage",
                message="Aucun Diagnostic Principal (DP) proposé - groupage impossible",
                affected_codes=[],
                suggested_action="Proposer un Diagnostic Principal",
            )
            groupage_errors.append(error)
            return None, None, groupage_errors

        # Mock: Générer un GHM/GHS fictif basé sur le DP
        # En production, ceci appellerait la FG ATIH réelle
        dp_code = all_codes["dp"][0]
        ghm = self._generate_mock_ghm(dp_code, all_codes, stay_metadata)
        ghs = self._generate_mock_ghs(ghm)

        # Vérifier la cohérence des codes (mock)
        coherence_errors = self._check_code_coherence(all_codes)
        groupage_errors.extend(coherence_errors)

        return ghm, ghs, groupage_errors

    def _generate_mock_ghm(
        self,
        dp_code: str,
        all_codes: Dict[str, List[str]],
        stay_metadata: StayMetadata,
    ) -> str:
        """
        Génère un GHM mock pour le POC.

        En production, ceci serait remplacé par l'appel à la FG ATIH.

        Args:
            dp_code: Code du Diagnostic Principal
            all_codes: Tous les codes
            stay_metadata: Métadonnées du séjour

        Returns:
            Code GHM mock
        """
        # Générer un GHM fictif basé sur le hash du DP
        # Format GHM: 2 chiffres + 1 lettre + 2 chiffres (ex: "05K02")
        hash_value = int(hashlib.md5(dp_code.encode()).hexdigest()[:8], 16)
        category = hash_value % 28 + 1  # Catégories 01-28
        severity = hash_value % 4 + 1  # Niveaux de sévérité 1-4

        # Lettre de catégorie (A-Z, sauf certaines)
        letters = "ABCDEFGHJKLMNPQRSTUVWXYZ"  # Sans I, O
        letter = letters[hash_value % len(letters)]

        ghm = f"{category:02d}{letter}{severity:02d}"
        return ghm

    def _generate_mock_ghs(self, ghm: str) -> str:
        """
        Génère un GHS mock basé sur le GHM.

        En production, ceci serait calculé par la FG ATIH.

        Args:
            ghm: Code GHM

        Returns:
            Code GHS mock
        """
        # Le GHS est souvent identique au GHM ou avec un suffixe
        # Pour le mock, on retourne le GHM tel quel
        return ghm

    def _check_code_coherence(
        self, all_codes: Dict[str, List[str]]
    ) -> List[ValidationIssue]:
        """
        Vérifie la cohérence des codes pour le groupage.

        Détecte les incompatibilités évidentes entre codes.

        Args:
            all_codes: Codes collectés par type

        Returns:
            Liste des erreurs de cohérence détectées
        """
        errors = []

        # Vérifier qu'il n'y a pas trop de DAS (limite arbitraire pour le mock)
        max_das = 20
        if len(all_codes["das"]) > max_das:
            error = ValidationIssue(
                issue_id=str(uuid.uuid4()),
                severity="a_revoir",
                category="groupage",
                message=f"Nombre élevé de DAS ({len(all_codes['das'])}) - "
                f"vérifier la pertinence",
                affected_codes=all_codes["das"],
                suggested_action="Revoir la liste des DAS et ne garder que les significatifs",
            )
            errors.append(error)

        # Vérifier qu'il n'y a pas trop d'actes CCAM (limite arbitraire)
        max_ccam = 30
        if len(all_codes["ccam"]) > max_ccam:
            error = ValidationIssue(
                issue_id=str(uuid.uuid4()),
                severity="a_revoir",
                category="groupage",
                message=f"Nombre élevé d'actes CCAM ({len(all_codes['ccam'])}) - "
                f"vérifier la pertinence",
                affected_codes=all_codes["ccam"],
                suggested_action="Revoir la liste des actes CCAM",
            )
            errors.append(error)

        return errors

    def get_version_info(self) -> Dict[str, str]:
        """
        Retourne les informations de version de la fonction de groupage.

        Returns:
            Dictionnaire avec version et métadonnées
        """
        return {
            "groupage_version": self.groupage_version,
            "implementation": "mock_poc",  # En production: "atih_official"
            "library_version": "N/A",  # En production: version de la bibliothèque ATIH
        }
