from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from typing import List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import select
from ..ml_models import (
    odd_classifier, odd_label_encoder, odd_names,
    nature_classifier, nature_label_encoder, nature_names,
    cible_classifier, indicateur_classifier,
    hierarchy_data
)
from ..database import get_db
from ..models import Activity, Direction, Ministry
import json
import os

router = APIRouter()

# --- Chargement des fichiers JSON avec débogage détaillé ---
print("\n" + "="*80)
print("🔍 DÉBOGAGE DÉTAILLÉ DU CHARGEMENT DES FICHIERS JSON")
print("="*80)

json_path = r"/var/www/html/mes-ia-odd/models/cibles_indicateurs.json"
ministry_odd_map_path = r"/var/www/html/mes-ia-odd/models/ministry_odd_mapping.json"

# Vérification du fichier cibles_indicateurs.json
print(f"\nVérification de: {json_path}")
print(f"   Existe? {os.path.exists(json_path)}")
print(f"   Chemin absolu: {os.path.abspath(json_path)}")
try:
    with open(json_path, "r", encoding="utf-8") as f:
        cibles_data = json.load(f)
    print(f" Chargé avec succès: {len(cibles_data)} éléments")
except Exception as e:
    print(f" Erreur: {e}")
    cibles_data = []

# Vérification détaillée du fichier ministry_odd_mapping.json
print(f"\n Vérification de: {ministry_odd_map_path}")
print(f"   Existe? {os.path.exists(ministry_odd_map_path)}")
print(f"   Chemin absolu: {os.path.abspath(ministry_odd_map_path)}")

ministry_odd_mapping = {"mappings": {}}

if os.path.exists(ministry_odd_map_path):
    try:
        print(f" Lecture du fichier...")
        with open(ministry_odd_map_path, "r", encoding="utf-8") as f:
            file_content = f.read()
            print(f"   Taille du fichier: {len(file_content)} caractères")
            print(f"   Premiers 200 caractères:\n{file_content[:200]}...")
            
        print(f"\n   Parsing JSON...")
        with open(ministry_odd_map_path, "r", encoding="utf-8") as f:
            ministry_odd_mapping = json.load(f)
            
        print(f"   JSON parsé avec succès!")
        print(f"\n   Structure du JSON:")
        print(f"     Type: {type(ministry_odd_mapping)}")
        print(f"     Clés principales: {list(ministry_odd_mapping.keys())}")
        
        if "mappings" in ministry_odd_mapping:
            mappings = ministry_odd_mapping["mappings"]
            print(f"      Type de 'mappings': {type(mappings)}")
            print(f"      Nombre de ministères: {len(mappings)}")
            print(f"\n   Toutes les clés dans 'mappings':")
            for key in sorted(mappings.keys(), key=lambda x: int(x)):
                ministry_name = mappings[key].get("ministry_name", "N/A")
                odd_ids = mappings[key].get("odd_ids", [])
                print(f"      • Clé: '{key}' (type: {type(key).__name__}) → {ministry_name} → ODDs: {odd_ids}")
        else:
            print(f"      ⚠️Clé 'mappings' non trouvée dans le JSON!")
            
    except json.JSONDecodeError as e:
        print(f"   Erreur de parsing JSON: {e}")
        print(f"      Position: ligne {e.lineno}, colonne {e.colno}")
        ministry_odd_mapping = {"mappings": {}}
    except Exception as e:
        print(f"   Erreur inattendue: {e}")
        import traceback
        print(f"      Traceback:\n{traceback.format_exc()}")
        ministry_odd_mapping = {"mappings": {}}
else:
    print(f"   Fichier non trouvé!")
    print(f"   Essayez de créer le fichier à cet emplacement")
    ministry_odd_mapping = {"mappings": {}}

print("\n" + "="*80)
print("CHARGEMENT TERMINÉ")
print("="*80 + "\n")

# --- Modèles Pydantic ---
class ActivityItem(BaseModel):
    name: str
    id: int

class ActivitiesRequest(BaseModel):
    activities: List[ActivityItem]

# --- Fonctions utilitaires ---
def get_first_prediction(preds):
    """Retourne le premier dict d'une liste ou liste imbriquée."""
    if not preds:
        return None
    first = preds[0]
    if isinstance(first, list) and first:
        return first[0]
    return first

def to_native(obj):
    import numpy as np
    if isinstance(obj, np.integer):
        return int(obj)
    if isinstance(obj, np.floating):
        return float(obj)
    if isinstance(obj, np.bool_):
        return bool(obj)
    return obj

def get_dynamic_values(nature_id):
    mapping = {
        1: {"sensitivity": 80, "appreciation_id": 4},
        2: {"sensitivity": 60, "appreciation_id": 3},
        3: {"sensitivity": 40, "appreciation_id": 2},
        4: {"sensitivity": 20, "appreciation_id": 1}
    }
    default_values = {"sensitivity": 20.00, "appreciation_id": 1}
    return mapping.get(nature_id, default_values)

# --- Construire un mapping index modèle → ID métier (au chargement) ---
nature_mapping = {}
try:
    classes = list(getattr(nature_label_encoder, "classes_", []))
    for idx, label_name in enumerate(classes):
        match = next((int(k) for k, v in nature_names.items() if v == label_name), None)
        nature_mapping[idx] = {"id": match, "name": label_name}
except Exception as e:
    print(f"Erreur lors de la construction du nature_mapping: {e}")

print("Mapping du modèle Nature → IDs métiers :", nature_mapping)

# --- Fonction pour récupérer le ministère d'une activité (VERSION DÉBUG) ---
def get_ministry_from_activity(activity_id: int, db: Session) -> Optional[int]:
    """
    Récupère l'ID du ministère à partir de l'ID de l'activité.
    VERSION AVEC DÉBOGAGE DÉTAILLÉ
    """
    print(f"\n   DÉBOGAGE get_ministry_from_activity(activity_id={activity_id})")
    print(f"   {'─'*70}")
    
    try:
        from sqlalchemy.orm import joinedload
        
        print(f"  Recherche de l'activité ID={activity_id}...")
        activity = db.query(Activity)\
            .options(joinedload(Activity.direction).joinedload(Direction.ministry))\
            .filter(Activity.id == activity_id)\
            .first()
        
        if not activity:
            print(f"   Activité non trouvée en base")
            return None
        
        print(f"   Activité trouvée: {activity.name[:50]}...")
        print(f"   Attributs de l'activité:")
        print(f"      • activity.id: {activity.id}")
        print(f"      • activity.__dict__.keys(): {list(activity.__dict__.keys())}")
        
        # Essayer plusieurs méthodes pour obtenir direction_id
        direction_id = None
        
        # Méthode 1: via __dict__
        if 'direction_id' in activity.__dict__:
            direction_id = activity.__dict__['direction_id']
            print(f"      • Méthode 1 (__dict__): direction_id = {direction_id}")
        
        # Méthode 2: via getattr
        try:
            direction_id_attr = getattr(activity, 'direction_id', None)
            print(f"      • Méthode 2 (getattr): direction_id = {direction_id_attr}")
            if direction_id is None:
                direction_id = direction_id_attr
        except Exception as e:
            print(f"      • Méthode 2 échec: {e}")
        
        # Méthode 3: via la relation direction
        try:
            if hasattr(activity, 'direction') and activity.direction:
                direction_id_rel = activity.direction.id
                print(f"      • Méthode 3 (relation): direction_id = {direction_id_rel}")
                if direction_id is None:
                    direction_id = direction_id_rel
        except Exception as e:
            print(f"      • Méthode 3 échec: {e}")
        
        if not direction_id:
            print(f"   Impossible de récupérer direction_id")
            return None
        
        print(f"\n   Recherche de la direction ID={direction_id}...")
        direction = db.query(Direction).filter(Direction.id == direction_id).first()
        
        if not direction:
            print(f"   Direction non trouvée")
            return None
        
        print(f"   Direction trouvée: {direction.name[:50]}...")
        print(f"   Attributs de la direction:")
        print(f"      • direction.id: {direction.id}")
        print(f"      • direction.__dict__.keys(): {list(direction.__dict__.keys())}")
        
        # Essayer plusieurs méthodes pour obtenir ministry_id
        ministry_id = None
        
        # Méthode 1: via __dict__
        if 'ministry_id' in direction.__dict__:
            ministry_id = direction.__dict__['ministry_id']
            print(f"      • Méthode 1 (__dict__): ministry_id = {ministry_id}")
        
        # Méthode 2: via getattr
        try:
            ministry_id_attr = getattr(direction, 'ministry_id', None)
            print(f"      • Méthode 2 (getattr): ministry_id = {ministry_id_attr}")
            if ministry_id is None:
                ministry_id = ministry_id_attr
        except Exception as e:
            print(f"      • Méthode 2 échec: {e}")
        
        # Méthode 3: via la relation ministry
        try:
            if hasattr(direction, 'ministry') and direction.ministry:
                ministry_id_rel = direction.ministry.id
                print(f"      • Méthode 3 (relation): ministry_id = {ministry_id_rel}")
                if ministry_id is None:
                    ministry_id = ministry_id_rel
        except Exception as e:
            print(f"      • Méthode 3 échec: {e}")

        if not ministry_id:
            print(f"   ❌ Impossible de récupérer ministry_id")
            return None
        ministry = db.query(Ministry).filter(Ministry.id == ministry_id).first()
        if ministry:
            print(f"   Ministère trouvé: {ministry.name}")
        else:
            print(f"   Ministère ID={ministry_id} existe mais pas récupéré")

        print(f"\n   RÉSULTAT FINAL: ministry_id = {ministry_id} (type: {type(ministry_id).__name__})")
        print(f"   {'─'*70}")
        
        return ministry_id
        
    except Exception as e:
        import traceback
        print(f"   ERREUR EXCEPTION:")
        print(f"      Message: {e}")
        print(f"      Traceback:\n{traceback.format_exc()}")
        return None

# --- Fonction pour obtenir les ODDs autorisés (VERSION DÉBUG) ---
def get_allowed_odds_for_ministry(ministry_id: int) -> List[int]:
    """
    Retourne la liste des IDs des ODDs autorisés pour un ministère donné.
    VERSION AVEC DÉBOGAGE DÉTAILLÉ
    """
    print(f"\n   DÉBOGAGE get_allowed_odds_for_ministry(ministry_id={ministry_id})")
    print(f"   {'─'*70}")
    
    # Afficher le type et la valeur exacte
    print(f"    Analyse de ministry_id:")
    print(f"      • Valeur: {ministry_id}")
    print(f"      • Type: {type(ministry_id).__name__}")
    print(f"      • repr(): {repr(ministry_id)}")
    
    # Convertir en string
    ministry_key = str(ministry_id)
    print(f"\n   Clé de recherche:")
    print(f"      • ministry_key: '{ministry_key}'")
    print(f"      • Type: {type(ministry_key).__name__}")
    print(f"      • Longueur: {len(ministry_key)}")
    print(f"      • repr(): {repr(ministry_key)}")
    
    # Afficher les détails du mapping
    print(f"\n   État du mapping:")
    print(f"      • Type de ministry_odd_mapping: {type(ministry_odd_mapping)}")
    print(f"      • Clés de ministry_odd_mapping: {list(ministry_odd_mapping.keys())}")
    
    mappings = ministry_odd_mapping.get("mappings", {})
    print(f"      • Type de mappings: {type(mappings)}")
    print(f"      • Nombre d'entrées dans mappings: {len(mappings)}")
    
    if mappings:
        print(f"\n   TOUTES les clés disponibles dans mappings:")
        for idx, key in enumerate(sorted(mappings.keys(), key=lambda x: int(x) if x.isdigit() else 999)):
            ministry_name = mappings[key].get("ministry_name", "N/A")
            print(f"      {idx+1:2d}. Clé: '{key}' (type: {type(key).__name__}, repr: {repr(key)}) → {ministry_name}")
        
        print(f"\n   Test de correspondance:")
        print(f"      • Recherche de la clé: '{ministry_key}'")
        print(f"      • ministry_key in mappings: {ministry_key in mappings}")
        print(f"      • ministry_key in mappings.keys(): {ministry_key in mappings.keys()}")
        
        # Test de comparaison exacte avec chaque clé
        print(f"\n   🧪 Comparaison exacte avec chaque clé:")
        for key in mappings.keys():
            is_equal = (key == ministry_key)
            is_equal_int = (int(key) == int(ministry_key)) if key.isdigit() and str(ministry_id).isdigit() else False
            print(f"      • '{key}' == '{ministry_key}': {is_equal} | int comparison: {is_equal_int}")
    else:
        print(f"    Le dictionnaire 'mappings' est vide!")
    
    # Tenter la recherche
    if ministry_key in mappings:
        allowed_odds = mappings[ministry_key]["odd_ids"]
        ministry_name = mappings[ministry_key].get("ministry_name", "Inconnu")
        print(f"\n   MAPPING TROUVÉ!")
        print(f"      • Ministère: {ministry_name}")
        print(f"      • ODDs autorisés: {allowed_odds}")
        print(f"   {'─'*70}")
        return allowed_odds
    else:
        print(f"\n   MAPPING NON TROUVÉ pour la clé '{ministry_key}'")
        print(f"      • Utilisation de tous les ODDs (1-17) par défaut")
        
        # Suggérer des solutions
        print(f"\n   SUGGESTIONS:")
        if mappings:
            close_matches = [k for k in mappings.keys() if str(ministry_id) in k or k in str(ministry_id)]
            if close_matches:
                print(f"      • Clés similaires trouvées: {close_matches}")
            
            # Vérifier si c'est un problème de type
            if str(ministry_id) in mappings:
                print(f"      • ATTENTION: La clé existe mais la comparaison échoue!")
                print(f"      • Vérifiez les espaces ou caractères invisibles")
        else:
            print(f"      • Le fichier JSON est vide ou mal chargé")
            print(f"      • Vérifiez le chemin: {ministry_odd_map_path}")
        
        print(f"   {'─'*70}")
        return list(range(1, 18))

# --- Fonction pour filtrer les prédictions ODD selon le ministère ---
def filter_odd_predictions(predictions, allowed_odd_ids: List[int]):
    """
    Filtre les prédictions du modèle ODD pour ne garder que celles
    qui correspondent aux ODDs autorisés pour le ministère.
    """
    if not predictions:
        return []
    
    preds_list = predictions[0] if isinstance(predictions[0], list) else predictions
    
    filtered = []
    for pred in preds_list:
        label = pred.get("label", "LABEL_0")
        odd_id = int(label.replace("LABEL_", "")) if isinstance(label, str) and label.startswith("LABEL_") else int(label)
        
        if odd_id in allowed_odd_ids:
            filtered.append(pred)
    
    filtered.sort(key=lambda x: x.get("score", 0), reverse=True)
    
    print(f"   Prédictions filtrées: {len(filtered)}/{len(preds_list)} ODDs conservés")
    for pred in filtered[:3]:
        label = pred.get("label", "LABEL_0")
        odd_id = int(label.replace("LABEL_", "")) if isinstance(label, str) and label.startswith("LABEL_") else int(label)
        score = pred.get("score", 0)
        print(f"      - ODD {odd_id}: score={score:.4f}")
    
    return filtered

# --- Fonction utilitaire pour convertir un label modèle en (idx, id_metier, nom) ---
def map_model_label_to_nature(label):
    """
    label: 'LABEL_0' ou '0' ou 0
    retourne: (model_idx, metier_id_or_None, class_name_or_None)
    """
    try:
        if isinstance(label, str) and label.upper().startswith("LABEL_"):
            model_idx = int(label.split("_", 1)[1])
        else:
            model_idx = int(label)
    except Exception:
        return None, None, None

    class_name = None
    try:
        if hasattr(nature_label_encoder, "inverse_transform"):
            class_name = nature_label_encoder.inverse_transform([model_idx])[0]
    except Exception:
        class_name = None

    if class_name is None:
        try:
            classes_attr = getattr(nature_label_encoder, "classes_", None)
            if classes_attr is not None and len(classes_attr) > model_idx:
                class_name = classes_attr[model_idx]
        except Exception:
            class_name = None

    if class_name:
        metier_id = next((int(k) for k, v in nature_names.items() if v == class_name), None)
        if metier_id is not None:
            return model_idx, metier_id, class_name

    info = nature_mapping.get(model_idx)
    if info:
        return model_idx, info.get("id"), info.get("name")

    return model_idx, None, class_name

# --- Endpoint principal ---
@router.post("/analyse-activities")
def analyse_activities(request: ActivitiesRequest, db: Session = Depends(get_db)):
    try:
        print(f"\n{'='*60}")
        print(f"DÉBUT DE L'ANALYSE - {len(request.activities)} activité(s) à traiter")
        print(f"{'='*60}")
        
        results = []
        for i, activity_item in enumerate(request.activities):
            act_name = activity_item.name
            activity_id = activity_item.id
            
            print(f"\n{'='*40}")
            print(f" ACTIVITÉ #{i+1}/{len(request.activities)}")
            print(f"   ID: {activity_id}")
            print(f"   Nom: '{act_name}'")
            print(f"{'='*40}")
  
            # --- 0. Récupérer le ministère de l'activité ---
            print("\n ÉTAPE 0: Récupération du ministère")
            ministry_id = get_ministry_from_activity(activity_id, db)
            
            if ministry_id is None:
                print("   Impossible de déterminer le ministère - Tous les ODDs seront considérés")
                allowed_odd_ids = list(range(1, 18))
            else:
                allowed_odd_ids = get_allowed_odds_for_ministry(ministry_id)

            # --- 1. Prédire ODD avec filtrage par ministère ---
            print("\n ÉTAPE 1: Prédiction ODD (filtrée par ministère)")
            odd_predictions = odd_classifier(act_name)
            print(f"   Prédictions brutes ODD: {len(odd_predictions[0]) if odd_predictions else 0} prédictions")
            
            filtered_odd_predictions = filter_odd_predictions(odd_predictions, allowed_odd_ids)
            
            if not filtered_odd_predictions:
                print("    Aucune prédiction ODD valide après filtrage - Activité ignorée")
                continue
            
            odd_pred = filtered_odd_predictions[0]
            print(f"   Meilleure prédiction ODD filtrée: {odd_pred}")
            
            odd_label = odd_pred.get("label", "LABEL_0")
            odd_id = int(odd_label.replace("LABEL_", "")) if isinstance(odd_label, str) and odd_label.startswith("LABEL_") else int(odd_label)
            odd_name = odd_names.get(str(odd_id), f"ODD {odd_id}")
            odd_score = odd_pred.get("score", 0)
            print(f"   ODD sélectionné: ID={odd_id}, Nom='{odd_name}', Score={odd_score:.4f}")

            # --- 2. Prédire Nature ---
            print("\n ÉTAPE 2: Prédiction Nature")
            nature_predictions = nature_classifier(act_name)
            
            nature_pred = get_first_prediction(nature_predictions)
            
            if not nature_pred:
                nature_id = int(next((k for k, v in nature_names.items() if v == "Soutien"), "4"))
                nature_name_str = nature_names.get(str(nature_id), "Soutien")
            else:
                nature_label = nature_pred.get("label", "LABEL_0")
                model_idx, mapped_id, mapped_name = map_model_label_to_nature(nature_label)

                if mapped_id is not None:
                    nature_id = mapped_id
                    nature_name_str = mapped_name
                else:
                    if mapped_name:
                        approx_match = next((int(k) for k, v in nature_names.items() if v == mapped_name), None)
                        if approx_match:
                            nature_id = approx_match
                            nature_name_str = mapped_name
                        else:
                            nature_id = int(next((k for k, v in nature_names.items() if v == "Soutien"), "4"))
                            nature_name_str = nature_names.get(str(nature_id), "Soutien")
                    else:
                        nature_id = int(next((k for k, v in nature_names.items() if v == "Soutien"), "4"))
                        nature_name_str = nature_names.get(str(nature_id), "Soutien")

            # --- 3. Filtrer les cibles possibles selon l'ODD ---
            print("\n ÉTAPE 3: Recherche des cibles pour l'ODD")
            possible_cibles = [c for c in cibles_data if str(c["odd_code"]) == str(odd_id)]
            
            cible_match = None
            if possible_cibles:
                cible_predictions = cible_classifier(act_name)
                cible_pred = get_first_prediction(cible_predictions)
                
                cible_id = None
                if cible_pred:
                    cible_label = cible_pred.get("label", "0")
                    try:
                        cible_id = int(cible_label.replace("LABEL_", "")) if isinstance(cible_label, str) and cible_label.startswith("LABEL_") else int(cible_label)
                    except:
                        cible_id = possible_cibles[0]["cible_id"]
                    cible_match = next((c for c in possible_cibles if c["cible_id"] == cible_id), possible_cibles[0])
                else:
                    cible_match = possible_cibles[0]

            # --- 4. Filtrer indicateurs ---
            print("\n ÉTAPE 4: Sélection de l'indicateur")
            indicateur_id = None
            indicateur_name = "Aucun indicateur disponible"
            
            if cible_match and "indicateurs" in cible_match and cible_match["indicateurs"]:
                indicateurs_disponibles = cible_match["indicateurs"]
                
                indicateur_predictions = indicateur_classifier(act_name)
                indicateur_pred = get_first_prediction(indicateur_predictions)
                
                if indicateur_pred:
                    ind_label = indicateur_pred.get("label", "0")
                    try:
                        ind_id = int(ind_label.replace("LABEL_", "")) if isinstance(ind_label, str) and ind_label.startswith("LABEL_") else int(ind_label)
                    except:
                        ind_id = 0
                    
                    ind_id = ind_id % len(indicateurs_disponibles)
                    
                    selected_indicateur = indicateurs_disponibles[ind_id]
                    indicateur_id = selected_indicateur.get("id")
                    indicateur_name = selected_indicateur.get("name", "Aucun indicateur disponible")

            # --- 5. Construire le résultat pour l'API ---
            result_item = {
                "activity": act_name,
                "activity_id": activity_id,
                "ministry_id": ministry_id,
                "odd": {"id": odd_id, "name": odd_name},
                "nature": {"id": nature_id, "name": nature_name_str},
                "cible": {"id": cible_match["cible_id"] if cible_match else None, "name": cible_match["cible"] if cible_match else "Inconnue"},
                "indicateur": {"id": indicateur_id, "name": indicateur_name}
            }
            
            results.append(result_item)
            print(f"\n Activité #{i+1} traitée avec succès!")

        print(f"\n{'='*60}")
        print(f" FIN DE L'ANALYSE - {len(results)} résultat(s) généré(s)")
        print(f"{'='*60}")
        
        final_response = {"results": results}
        
        return final_response

    except Exception as e:
        import traceback
        error_details = f"Erreur analyse : {str(e)}\nTraceback: {traceback.format_exc()}"
        print(f"\n ERREUR GLOBALE: {error_details}")
        raise HTTPException(status_code=500, detail=error_details)
