"""Shortest-path computation over AGE graphs via NetworkX."""

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 _load_graph(
    driver: PiggieDriver,
    graph: str,
    label: str | None,
    rel_type: str | None,
) -> Any:
    """Load an AGE graph as a NetworkX graph."""
    try:
        import networkx as nx  # noqa: F401
    except ImportError:
        raise ImportError(
            "networkx is required. Install with: pip install piggie[networkx]"
        ) from None

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


def shortest_path(
    driver: PiggieDriver,
    graph: str,
    source: int,
    target: int,
    *,
    weight: str | None = None,
    label: str | None = None,
    rel_type: str | None = None,
) -> list[int]:
    """Find the shortest path between two nodes.

    Args:
        driver: Connected PiggieDriver.
        graph: Graph name.
        source: Source node ID (AGE vertex id).
        target: Target node ID (AGE vertex id).
        weight: Edge property to use as weight. None = unweighted.
        label: Filter to a single vertex label.
        rel_type: Filter to a single edge type.

    Returns:
        List of node IDs forming the shortest path.
    """
    import networkx as nx

    G = _load_graph(driver, graph, label, rel_type)

    try:
        path: list[Any] = nx.shortest_path(G, source=source, target=target, weight=weight)
    except nx.NetworkXNoPath:
        return []
    except nx.NodeNotFound as exc:
        raise AlgorithmError(f"Node not found: {exc}") from exc
    except Exception as exc:
        raise AlgorithmError(f"Shortest path failed: {exc}") from exc

    return [int(n) for n in path]


def dijkstra(
    driver: PiggieDriver,
    graph: str,
    source: int,
    target: int,
    *,
    weight: str = "weight",
    label: str | None = None,
    rel_type: str | None = None,
) -> pd.DataFrame:
    """Find the shortest weighted path using Dijkstra's algorithm.

    Args:
        driver: Connected PiggieDriver.
        graph: Graph name.
        source: Source node ID (AGE vertex id).
        target: Target node ID (AGE vertex id).
        weight: Edge property to use as weight (default "weight").
        label: Filter to a single vertex label.
        rel_type: Filter to a single edge type.

    Returns:
        DataFrame with columns [path, total_weight].
        path is a list of node IDs, total_weight is the sum of edge weights.
        Empty DataFrame if no path exists.
    """
    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

    G = _load_graph(driver, graph, label, rel_type)

    try:
        distance, path = nx.single_source_dijkstra(G, source, target, weight=weight)
    except nx.NetworkXNoPath:
        return pd.DataFrame(columns=["path", "total_weight"])
    except nx.NodeNotFound as exc:
        raise AlgorithmError(f"Node not found: {exc}") from exc
    except Exception as exc:
        raise AlgorithmError(f"Dijkstra failed: {exc}") from exc

    return pd.DataFrame([{"path": [int(n) for n in path], "total_weight": distance}])


def all_shortest_paths(
    driver: PiggieDriver,
    graph: str,
    source: int,
    target: int,
    *,
    weight: str | None = None,
    label: str | None = None,
    rel_type: str | None = None,
) -> list[list[int]]:
    """Find all shortest paths between two nodes.

    Args:
        driver: Connected PiggieDriver.
        graph: Graph name.
        source: Source node ID (AGE vertex id).
        target: Target node ID (AGE vertex id).
        weight: Edge property to use as weight. None = unweighted.
        label: Filter to a single vertex label.
        rel_type: Filter to a single edge type.

    Returns:
        List of paths, where each path is a list of node IDs.
        Empty list if no path exists.
    """
    import networkx as nx

    G = _load_graph(driver, graph, label, rel_type)

    try:
        paths = list(nx.all_shortest_paths(G, source=source, target=target, weight=weight))
    except nx.NetworkXNoPath:
        return []
    except nx.NodeNotFound as exc:
        raise AlgorithmError(f"Node not found: {exc}") from exc
    except Exception as exc:
        raise AlgorithmError(f"All shortest paths failed: {exc}") from exc

    return [[int(n) for n in p] for p in paths]
