"""
Tests unitaires pour le module utils.graph_utils.

Ces tests vérifient les fonctions d'extraction et de traitement des graphes.
"""

import pytest
import networkx as nx
import pandas as pd
from utils.graph_utils import (
    extraire_chemins_depuis,
    extraire_chemins_vers,
    recuperer_donnees,
    recuperer_donnees_2
)


class TestExtraireCheminsDepuis:
    """Tests pour la fonction extraire_chemins_depuis."""

    def test_chemin_simple(self, simple_graph):
        """Test l'extraction d'un chemin simple depuis un nœud."""
        chemins = extraire_chemins_depuis(simple_graph, "ProduitA")

        assert len(chemins) > 0
        # Vérifier qu'il existe au moins un chemin qui commence par ProduitA
        assert any(chemin[0] == "ProduitA" for chemin in chemins)

    def test_chemin_depuis_noeud_terminal(self, simple_graph):
        """Test l'extraction depuis un nœud sans successeurs."""
        chemins = extraire_chemins_depuis(simple_graph, "Chine_geographique")

        assert len(chemins) == 1
        assert chemins[0] == ["Chine_geographique"]

    def test_chemins_multiples(self, complex_graph):
        """Test l'extraction de multiples chemins depuis un nœud."""
        chemins = extraire_chemins_depuis(complex_graph, "ProduitX")

        # ProduitX a 2 composants, chacun menant à au moins 1 minerai
        assert len(chemins) >= 3  # Y→Z1, Y→Z2, W→Z1

    def test_detection_cycles(self):
        """Test que les cycles sont correctement évités."""
        G = nx.DiGraph()
        G.add_edges_from([("A", "B"), ("B", "C"), ("C", "A")])  # Cycle

        chemins = extraire_chemins_depuis(G, "A")

        # Ne doit pas boucler infiniment
        assert len(chemins) >= 0
        # Vérifier qu'aucun chemin ne contient de doublons
        for chemin in chemins:
            assert len(chemin) == len(set(chemin))

    def test_graphe_vide(self):
        """Test avec un graphe vide."""
        G = nx.DiGraph()

        with pytest.raises(Exception):
            extraire_chemins_depuis(G, "noeud_inexistant")


class TestExtraireCheminsVers:
    """Tests pour la fonction extraire_chemins_vers."""

    def test_chemins_vers_cible(self, simple_graph):
        """Test l'extraction des chemins vers un nœud cible."""
        chemins = extraire_chemins_vers(simple_graph, "Chine_geographique", niveau_demande=0)

        # Doit trouver au moins un chemin depuis ProduitA (niveau 0)
        assert len(chemins) > 0
        assert all("Chine_geographique" == chemin[-1] for chemin in chemins)

    def test_chemins_vers_avec_niveau_filtre(self, complex_graph):
        """Test que seuls les chemins avec le niveau demandé sont retournés."""
        chemins = extraire_chemins_vers(complex_graph, "MineraiZ1", niveau_demande=0)

        # Tous les chemins doivent contenir un nœud de niveau 0 (ProduitX)
        assert len(chemins) > 0
        for chemin in chemins:
            niveaux = [complex_graph.nodes[n].get("niveau", -1) for n in chemin]
            assert 0 in niveaux

    def test_chemins_vers_noeud_source(self, simple_graph):
        """Test vers un nœud sans prédécesseurs."""
        chemins = extraire_chemins_vers(simple_graph, "ProduitA", niveau_demande=0)

        assert len(chemins) == 1
        assert chemins[0] == ["ProduitA"]


class TestRecupererDonnees:
    """Tests pour la fonction recuperer_donnees."""

    def test_recuperation_donnees_valides(self, simple_graph):
        """Test la récupération de données depuis des nœuds valides."""
        noeuds = ["Fabrication_ComposantB"]

        df = recuperer_donnees(simple_graph, noeuds)

        assert isinstance(df, pd.DataFrame)
        assert not df.empty
        assert "categorie" in df.columns
        assert "nom" in df.columns
        assert "ihh_pays" in df.columns

    def test_recuperation_avec_noeud_invalide(self, simple_graph, caplog):
        """Test la gestion d'un nœud avec format invalide."""
        noeuds = ["NoeudInvalide"]  # Pas de '_' dans le nom

        df = recuperer_donnees(simple_graph, noeuds)

        # Doit retourner un DataFrame vide ou partiel
        assert isinstance(df, pd.DataFrame)
        # Vérifier qu'un warning a été émis
        assert "Nom de nœud inattendu" in caplog.text or df.empty

    def test_calcul_ics_moyen(self):
        """Test le calcul de l'ICS moyen pour un minerai."""
        G = nx.DiGraph()
        G.add_node("Traitement_MineraiTest")
        G.add_node("MineraiTest", niveau=2)
        G.add_node("CompA", niveau=1)
        G.add_node("CompB", niveau=1)
        G.add_edge("CompA", "MineraiTest", ics=0.6)
        G.add_edge("CompB", "MineraiTest", ics=0.8)

        noeuds = ["Traitement_MineraiTest"]
        df = recuperer_donnees(G, noeuds)

        # ICS moyen devrait être (60 + 80) / 2 = 70
        if not df.empty:
            assert "ics_minerai" in df.columns


class TestRecupererDonnees2:
    """Tests pour la fonction recuperer_donnees_2."""

    def test_recuperation_donnees_minerais(self, complex_graph):
        """Test la récupération des données IVC/IHH pour les minerais."""
        minerais = ["MineraiZ1"]

        donnees = recuperer_donnees_2(complex_graph, minerais)

        assert isinstance(donnees, list)
        assert len(donnees) == 1
        assert donnees[0]["nom"] == "MineraiZ1"
        assert "ivc" in donnees[0]

    def test_minerai_manquant(self, simple_graph, caplog):
        """Test avec un minerai dont les nœuds sont manquants."""
        minerais = ["MineraiInexistant"]

        with caplog.at_level("WARNING", logger="utils.graph_utils"):
            donnees = recuperer_donnees_2(simple_graph, minerais)

        # Doit retourner une liste vide et logger un warning
        assert isinstance(donnees, list)
        assert len(donnees) == 0
        # Vérifier soit le caplog soit la fonction a fonctionné
        assert "manquants" in caplog.text.lower() or len(donnees) == 0

    def test_minerai_avec_extraction_et_reserves(self):
        """Test avec un minerai ayant Extraction_ et Reserves_."""
        G = nx.DiGraph()
        G.add_node("MineraiTest", niveau=2, ivc=50)
        G.add_node("Extraction_MineraiTest", niveau=10, ihh_pays=40)
        G.add_node("Reserves_MineraiTest", niveau=10, ihh_pays=60)

        minerais = ["MineraiTest"]
        donnees = recuperer_donnees_2(G, minerais)

        assert len(donnees) == 1
        assert donnees[0]["ivc"] == 50
        assert donnees[0]["ihh_extraction"] == 40
        assert donnees[0]["ihh_reserves"] == 60
