import logging
import pathlib

import networkx as nx
import pandas as pd
import streamlit as st
import yaml
from networkx.drawing.nx_agraph import read_dot

from utils.logger import setup_logger

logger = setup_logger(__name__)

# Configuration Gitea
from config import DOT_FILE
from utils.gitea import charger_schema_depuis_gitea


def extraire_chemins_depuis(G, source):
    """Extrait tous les chemins depuis un noeud source jusqu'aux feuilles du graphe.

    Utilise un parcours en profondeur iteratif (DFS) pour explorer tous les chemins
    possibles depuis le noeud source. Evite les cycles en verifiant que chaque noeud
    n'apparait qu'une fois par chemin.

    Args:
        G: Graphe NetworkX dirige.
        source: Noeud de depart.

    Returns:
        list[list[str]]: Liste de chemins, ou chaque chemin est une liste de noeuds.
    """
    chemins = []
    stack = [(source, [source])]
    while stack:
        (node, path) = stack.pop()
        voisins = list(G.successors(node))
        if not voisins:
            chemins.append(path)
        else:
            for voisin in voisins:
                if voisin not in path:
                    stack.append((voisin, path + [voisin]))
    return chemins


def extraire_chemins_vers(G, target, niveau_demande):
    """Extrait tous les chemins vers un noeud cible contenant un niveau specifique.

    Parcourt le graphe inverse depuis la cible vers les racines, et filtre uniquement
    les chemins qui contiennent au moins un noeud du niveau demande.

    Args:
        G: Graphe NetworkX dirige.
        target: Noeud d'arrivee.
        niveau_demande: Niveau hierarchique requis dans le chemin (0=Produit, 1=Composant, etc.).

    Returns:
        list[list[str]]: Liste de chemins (du niveau demande vers la cible) qui contiennent
                         au moins un noeud du niveau demande.
    """
    chemins = []
    reverse_G = G.reverse()
    niveaux = nx.get_node_attributes(G, "niveau")
    stack = [(target, [target])]

    while stack:
        (node, path) = stack.pop()
        voisins = list(reverse_G.successors(node))
        if not voisins:
            chemin_inverse = list(reversed(path))
            contient_niveau = any(
                int(niveaux.get(n, -1)) == niveau_demande for n in chemin_inverse
            )
            if contient_niveau:
                chemins.append(chemin_inverse)
        else:
            for voisin in voisins:
                if voisin not in path:
                    stack.append((voisin, path + [voisin]))

    return chemins


def recuperer_donnees(graph, noeuds):
    """Recupere les donnees IHH et ICS pour les noeuds d'operations-minerais.

    Calcule l'ICS moyen pour chaque minerai base sur les aretes entrantes depuis
    les fabrications, puis extrait les donnees IHH (pays/acteurs) pour chaque operation.

    Args:
        graph: Graphe NetworkX contenant les attributs ihh_pays, ihh_acteurs, ics.
        noeuds: Liste de noeuds au format "Operation_Minerai" (ex: "Traitement_Lithium").

    Returns:
        pd.DataFrame: DataFrame avec colonnes categorie, nom, ihh_pays, ihh_acteurs, ics_minerai, ics_cat.
    """
    donnees = []
    ics = {}

    for noeud in noeuds:
        try:
            operation, minerai = noeud.split('_', 1)
        except ValueError:
            logging.warning(f"Nom de nœud inattendu : {noeud}")
            continue

        if operation == "Traitement":
            try:
                fabrications = list(graph.predecessors(minerai))
                valeurs = [
                    int(float(graph.get_edge_data(f, minerai)[0].get('ics', 0)) * 100)
                    for f in fabrications
                    if graph.get_edge_data(f, minerai)
                ]
                if valeurs:
                    ics[minerai] = round(sum(valeurs) / len(valeurs))
            except Exception as e:
                logging.warning(f"Erreur criticité pour {noeud} : {e}")
                ics[minerai] = 50

    for noeud in noeuds:
        try:
            operation, minerai = noeud.split('_', 1)
            ihh_pays = int(graph.nodes[noeud].get('ihh_pays', 0))
            ihh_acteurs = int(graph.nodes[noeud].get('ihh_acteurs', 0))
            ics_val = ics.get(minerai, 50)
            ics_cat = 1 if ics_val <= 33 else (2 if ics_val <= 66 else 3)

            donnees.append({
                'categorie': operation,
                'nom': minerai,
                'ihh_pays': ihh_pays,
                'ihh_acteurs': ihh_acteurs,
                'ics_minerai': ics_val,
                'ics_cat': ics_cat
            })
        except Exception as e:
            logging.error(f"Erreur sur le nœud {noeud} : {e}", exc_info=True)

    return pd.DataFrame(donnees)


def recuperer_donnees_2(graph, noeuds_2):
    """Recupere les donnees IVC et IHH pour les minerais (niveau 2).

    Extrait l'IVC du minerai et les IHH d'extraction/reserves pour chaque minerai.
    Ignore les minerais dont les noeuds associes sont manquants.

    Args:
        graph: Graphe NetworkX contenant les attributs ivc, ihh_pays.
        noeuds_2: Liste de noms de minerais (niveau 2).

    Returns:
        list[dict]: Liste de dictionnaires avec cles nom, ivc, ihh_extraction, ihh_reserves.
    """
    donnees = []
    for minerai in noeuds_2:
        try:
            missing = []
            if not graph.has_node(minerai):
                missing.append(minerai)
            if not graph.has_node(f"Extraction_{minerai}"):
                missing.append(f"Extraction_{minerai}")
            if not graph.has_node(f"Reserves_{minerai}"):
                missing.append(f"Reserves_{minerai}")

            if missing:
                logger.warning(f"Nœuds manquants pour {minerai} : {', '.join(missing)} — Ignoré.")
                continue

            ivc = int(graph.nodes[minerai].get('ivc', 0))
            ihh_extraction_pays = int(graph.nodes[f"Extraction_{minerai}"].get('ihh_pays', 0))
            ihh_reserves_pays = int(graph.nodes[f"Reserves_{minerai}"].get('ihh_pays', 0))

            donnees.append({
                'nom': minerai,
                'ivc': ivc,
                'ihh_extraction': ihh_extraction_pays,
                'ihh_reserves': ihh_reserves_pays
            })
        except Exception as e:
            logger.error(f"Erreur avec le nœud {minerai} : {e}", exc_info=True)
    return donnees


def load_seuils_config(path: str = "assets/config.yaml") -> dict:
    """Charge les seuils depuis le fichier de configuration YAML.

    Args:
        path (str): Chemin vers le fichier de configuration.

    Returns:
        dict: Dictionnaire contenant les seuils pour chaque indice.
    """
    try:
        data = yaml.safe_load(pathlib.Path(path).read_text(encoding="utf-8"))
        return data.get("seuils", {})
    except Exception as e:
        logging.warning(f"Erreur lors du chargement des seuils depuis {path}: {e}")
        # Valeurs par défaut en cas d'erreur
        return {
            "ISG": {"vert": {"max": 40}, "orange": {"min": 40, "max": 70}, "rouge": {"min": 70}},
            "IHH": {"vert": {"max": 15}, "orange": {"min": 15, "max": 25}, "rouge": {"min": 25}},
            "IVC": {"vert": {"max": 15}, "orange": {"min": 15, "max": 60}, "rouge": {"min": 60}}
        }


def determiner_couleur_par_seuil(valeur: int, seuils_indice: dict) -> str:
    """Détermine la couleur en fonction de la valeur et des seuils configurés.
    Logique alignée avec determine_threshold_color du projet.

    Args:
        valeur (int): Valeur de l'indice à évaluer.
        seuils_indice (dict): Seuils pour cet indice depuis la configuration.

    Returns:
        str: Couleur correspondante ("darkgreen", "orange", "darkred", "gray").
    """
    if valeur < 0:
        return "gray"

    # Vérifier d'abord le seuil rouge (priorité la plus haute)
    if "rouge" in seuils_indice and "min" in seuils_indice["rouge"]:
        if valeur >= seuils_indice["rouge"]["min"]:
            return "darkred"

    # Ensuite le seuil orange
    if "orange" in seuils_indice and "min" in seuils_indice["orange"] and "max" in seuils_indice["orange"]:
        if valeur >= seuils_indice["orange"]["min"] and valeur < seuils_indice["orange"]["max"]:
            return "orange"

    # Seuil vert (valeurs inférieures au seuil orange)
    if "vert" in seuils_indice and "max" in seuils_indice["vert"]:
        if valeur < seuils_indice["vert"]["max"]:
            return "darkgreen"

    # Par défaut orange si on ne trouve pas de correspondance exacte
    return "orange"


def couleur_noeud(n: str, niveaux: dict, G: nx.DiGraph) -> str:
    """Détermine la couleur d'un nœud en fonction de son niveau et de ses attributs.
    Utilise les seuils définis dans le fichier de configuration.

    Args:
        n (str): Nom du nœud.
        niveaux (dict): Dictionnaire associant chaque nœud à son niveau.
        G (nx.DiGraph): Graphe NetworkX contenant les données.

    Returns:
        str: Couleur du nœud.
    """
    niveau = niveaux.get(n, 99)
    attrs = G.nodes[n]
    seuils = load_seuils_config()

    # Niveau 99 : pays géographique avec isg
    if niveau == 99:
        isg = int(attrs.get("isg", -1))
        if isg >= 0 and "ISG" in seuils:
            return determiner_couleur_par_seuil(isg, seuils["ISG"])
        return "gray"

    # Niveau 11 ou 12 connecté à un pays géographique
    if niveau in (11, 12, 1011, 1012):
        for succ in G.successors(n):
            if niveaux.get(succ) == 99:
                isg = int(G.nodes[succ].get("isg", -1))
                if isg >= 0 and "ISG" in seuils:
                    return determiner_couleur_par_seuil(isg, seuils["ISG"])
                return "gray"

    # Logique existante pour IHH / IVC
    if niveau in (10, 1010) and attrs.get("ihh_pays"):
        ihh = int(attrs["ihh_pays"])
        if "IHH" in seuils:
            return determiner_couleur_par_seuil(ihh, seuils["IHH"])
        # Fallback vers les anciennes valeurs
        return (
            "darkgreen" if ihh <= 15 else
            "orange" if ihh <= 25 else
            "darkred"
        )
    if niveau == 2 and attrs.get("ivc"):
        ivc = int(attrs["ivc"])
        if "IVC" in seuils:
            return determiner_couleur_par_seuil(ivc, seuils["IVC"])
        # Fallback vers les anciennes valeurs
        return (
            "darkgreen" if ivc <= 15 else
            "orange" if ivc <= 30 else
            "darkred"
        )

    return "lightblue"

def charger_graphe():
    """Charge le graphe DOT depuis Gitea et le stocke dans st.session_state.

    Telecharge le schema DOT depuis Gitea (avec cache local), parse le fichier DOT
    en graphe NetworkX, et stocke le resultat dans session_state. Cree egalement
    une copie pour les visualisations IVC.

    Returns:
        bool: True si le graphe a ete charge avec succes, False sinon.

    Note:
        Le graphe est stocke dans st.session_state["G_temp"] et st.session_state["G_temp_ivc"].
    """
    if "G_temp" not in st.session_state:
            try:
                if charger_schema_depuis_gitea(DOT_FILE):
                    st.session_state["G_temp"] = read_dot(DOT_FILE)
                    st.session_state["G_temp_ivc"] = st.session_state["G_temp"].copy()
                    dot_file_path = True
                else:
                    dot_file_path = False
            except Exception as e:
                st.error(f"Erreur de lecture du fichier DOT : {e}")
                dot_file_path = False
    else:
        dot_file_path = True

    if dot_file_path:
        return dot_file_path
    st.error("Impossible de charger le graphe pour cet onglet.")
    return dot_file_path
