"""Centrality measures over AGE graphs via NetworkX or igraph."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from piggie._integrations.networkx import to_networkx
from piggie.exceptions import AlgorithmError

if TYPE_CHECKING:
    import pandas as pd

    from piggie.driver import PiggieDriver


def _resolve_backend(backend: str, measure: str = "") -> str:
    if backend != "auto":
        return backend
    # networkit is fastest for closeness/harmonic (multi-threaded C++)
    if measure in ("closeness", "harmonic"):
        try:
            import networkit  # noqa: F401
            return "networkit"
        except ImportError:
            pass
    try:
        import igraph  # noqa: F401
        return "igraph"
    except ImportError:
        return "networkx"


def _centrality_networkit(
    driver: PiggieDriver,
    graph: str,
    *,
    measure: str,
    label: str | None,
    rel_type: str | None,
) -> pd.DataFrame:
    import networkit as nk
    import pandas as pd

    from piggie._integrations.igraph_integration import to_igraph_sql

    # Load via igraph (fast SQL path), then convert to networkit
    G_ig, id_map = to_igraph_sql(driver, graph, directed=True)
    G_ig_u = G_ig.as_undirected()

    G_nk = nk.Graph(G_ig_u.vcount(), weighted=False, directed=False)
    for e in G_ig_u.get_edgelist():
        if not G_nk.hasEdge(e[0], e[1]):
            G_nk.addEdge(e[0], e[1])

    try:
        if measure == "closeness":
            algo = nk.centrality.Closeness(
                G_nk, True, nk.centrality.ClosenessVariant.Generalized,
            )
            algo.run()
            scores = algo.scores()
        elif measure == "harmonic":
            algo = nk.centrality.HarmonicCloseness(G_nk)
            algo.run()
            scores = algo.scores()
        else:
            raise AlgorithmError(
                f"networkit backend does not support measure: {measure!r}"
            )
    except AlgorithmError:
        raise
    except Exception as exc:
        raise AlgorithmError(f"Centrality computation failed: {exc}") from exc

    rows = []
    for idx, score in enumerate(scores):
        rows.append({
            "node_id": id_map[idx],
            "label": G_ig.vs[idx]["label"],
            "centrality": score,
        })
    return pd.DataFrame(rows)


def _centrality_igraph(
    driver: PiggieDriver,
    graph: str,
    *,
    measure: str,
    label: str | None,
    rel_type: str | None,
) -> pd.DataFrame:
    import pandas as pd

    from piggie._integrations.igraph_integration import to_igraph_sql

    G_ig, id_map = to_igraph_sql(driver, graph, directed=True)

    try:
        if measure == "degree":
            raw = G_ig.degree()
            n = G_ig.vcount()
            denom = max(n - 1, 1)
            scores = [v / denom for v in raw]
        elif measure == "betweenness":
            scores = G_ig.betweenness(directed=True)
        elif measure == "closeness":
            scores = G_ig.closeness(mode="all")
        elif measure == "eigenvector":
            G_u = G_ig.as_undirected()
            scores = G_u.eigenvector_centrality()
        elif measure == "harmonic":
            scores = G_ig.harmonic_centrality(mode="all")
        else:
            raise AlgorithmError(
                f"Unknown centrality measure: {measure!r}. "
                "Valid measures: degree, betweenness, closeness, eigenvector, harmonic"
            )
    except AlgorithmError:
        raise
    except Exception as exc:
        raise AlgorithmError(f"Centrality computation failed: {exc}") from exc

    rows = []
    for idx, score in enumerate(scores):
        rows.append({
            "node_id": id_map[idx],
            "label": G_ig.vs[idx]["label"],
            "centrality": score,
        })
    return pd.DataFrame(rows)


def centrality(
    driver: PiggieDriver,
    graph: str,
    *,
    measure: str = "degree",
    backend: str = "auto",
    label: str | None = None,
    rel_type: str | None = None,
    approximate: bool = False,
    k: int | None = None,
) -> pd.DataFrame:
    """Compute centrality for nodes in an AGE graph.

    Args:
        driver: Connected PiggieDriver.
        graph: Graph name.
        measure: Centrality measure — "degree", "betweenness", "closeness",
            "eigenvector", or "harmonic".
        backend: Compute backend — "auto" (prefer networkit for
            closeness/harmonic, igraph for others), "networkit", "igraph",
            or "networkx".
        label: Filter to a single vertex label.
        rel_type: Filter to a single edge type.
        approximate: Use approximate algorithms where available (NetworkX
            only). For betweenness, uses k-sample approximation.
        k: Sample size for approximate betweenness. Defaults to
            min(100, num_nodes) when approximate=True.

    Returns:
        DataFrame with columns [node_id, label, centrality].
    """
    resolved = _resolve_backend(backend, measure=measure)

    if resolved == "networkit" and not approximate:
        return _centrality_networkit(
            driver, graph, measure=measure, label=label, rel_type=rel_type,
        )

    if resolved == "igraph" and not approximate:
        return _centrality_igraph(
            driver, graph, measure=measure, label=label, rel_type=rel_type,
        )

    # NetworkX path (also used for approximate=True since igraph has no k-sample)
    try:
        import networkx as nx
        import pandas as pd
    except ImportError as exc:
        raise ImportError(
            "networkx and pandas are required. "
            "Install with: pip install piggie[networkx,pandas]"
        ) from exc

    labels = [label] if label else None
    rel_types = [rel_type] if rel_type else None
    G = to_networkx(driver, graph, labels=labels, rel_types=rel_types)

    try:
        if measure == "degree":
            scores: dict[Any, float] = nx.degree_centrality(G)
        elif measure == "betweenness":
            if approximate:
                sample_k = k if k is not None else min(100, len(G))
                scores = nx.betweenness_centrality(G, k=sample_k)
            else:
                scores = nx.betweenness_centrality(G)
        elif measure == "closeness":
            scores = nx.closeness_centrality(G)
        elif measure == "eigenvector":
            try:
                scores = nx.eigenvector_centrality_numpy(G)
            except nx.NetworkXException:
                largest_scc = max(nx.strongly_connected_components(G), key=len)
                sub_g = G.subgraph(largest_scc)
                try:
                    sub_scores = nx.eigenvector_centrality_numpy(sub_g)
                except nx.NetworkXException:
                    sub_scores = nx.eigenvector_centrality(
                        sub_g, max_iter=1000, tol=1e-6
                    )
                scores = {n: sub_scores.get(n, 0.0) for n in G.nodes()}
        elif measure == "harmonic":
            scores = nx.harmonic_centrality(G)
        else:
            raise AlgorithmError(
                f"Unknown centrality measure: {measure!r}. "
                "Valid measures: degree, betweenness, closeness, eigenvector, harmonic"
            )
    except AlgorithmError:
        raise
    except Exception as exc:
        raise AlgorithmError(f"Centrality computation failed: {exc}") from exc

    rows = []
    for node_id, score in scores.items():
        node_label = G.nodes[node_id].get("label", "")
        rows.append({"node_id": node_id, "label": node_label, "centrality": score})

    return pd.DataFrame(rows)
