"""
Network metrics calculation for Woseco concept graphs.
"""
import networkx as nx
import numpy as np
from typing import List, Dict, Optional


def calculate_aspl(graph: nx.Graph) -> float:
    """
    Calculate Average Shortest Path Length (ASPL).

    ASPL measures the average distance between all pairs of nodes in the graph.
    Lower values indicate more efficient concept connections.

    Args:
        graph: NetworkX graph

    Returns:
        Average shortest path length, or 0 if graph is empty/disconnected
    """
    if len(graph) == 0:
        return 0.0

    if not nx.is_connected(graph):
        # For disconnected graphs, calculate for largest component
        largest_cc = max(nx.connected_components(graph), key=len)
        subgraph = graph.subgraph(largest_cc)
        if len(subgraph) <= 1:
            return 0.0
        return nx.average_shortest_path_length(subgraph)

    return nx.average_shortest_path_length(graph)


def calculate_mlcc(graph: nx.Graph) -> float:
    """
    Calculate Mean Local Clustering Coefficient (MLCC).

    MLCC measures the degree to which nodes cluster together.
    Higher values indicate more tightly knit concept groups.

    Args:
        graph: NetworkX graph

    Returns:
        Mean local clustering coefficient
    """
    if len(graph) == 0:
        return 0.0

    return nx.average_clustering(graph)


def calculate_modularity(graph: nx.Graph) -> float:
    """
    Calculate Modularity (Q).

    Modularity measures the strength of division into communities/modules.
    Higher values indicate stronger community structure.

    Args:
        graph: NetworkX graph

    Returns:
        Modularity score
    """
    if len(graph) == 0:
        return 0.0

    # Use greedy modularity communities algorithm
    communities = nx.algorithms.community.greedy_modularity_communities(graph)
    return nx.algorithms.community.modularity(graph, communities)


def calculate_num_components(graph: nx.Graph) -> int:
    """
    Calculate Number of Connected Components (NGCC).

    Args:
        graph: NetworkX graph

    Returns:
        Number of connected components
    """
    return nx.number_connected_components(graph)


def calculate_avg_component_size(graph: nx.Graph) -> float:
    """
    Calculate average size of connected components.

    Args:
        graph: NetworkX graph

    Returns:
        Average component size
    """
    if len(graph) == 0:
        return 0.0

    components = list(nx.connected_components(graph))
    if not components:
        return 0.0

    sizes = [len(c) for c in components]
    return np.mean(sizes)


def calculate_gcc_size(graph: nx.Graph) -> int:
    """
    Calculate Giant Connected Component (GCC) size.

    The GCC is the largest connected component in the graph.

    Args:
        graph: NetworkX graph

    Returns:
        Size of the largest connected component
    """
    if len(graph) == 0:
        return 0

    components = nx.connected_components(graph)
    return len(max(components, key=len))


def calculate_all_metrics(graph: nx.Graph) -> Dict[str, float]:
    """
    Calculate all network metrics for a graph.

    Args:
        graph: NetworkX graph

    Returns:
        Dictionary with all metrics
    """
    return {
        "aspl": calculate_aspl(graph),
        "mlcc": calculate_mlcc(graph),
        "modularity": calculate_modularity(graph),
        "num_components": calculate_num_components(graph),
        "avg_component_size": calculate_avg_component_size(graph),
        "gcc_size": calculate_gcc_size(graph),
    }


def stats(woseco_lists: List[List[str]], graph_type: str = "chain") -> Dict[str, List[float]]:
    """
    Calculate network statistics for multiple Woseco concept lists.

    This is the main function from the original notebook that computes
    6 network metrics across multiple stories.

    Args:
        woseco_lists: List of concept lists (one per story)
        graph_type: Type of graph to build ("chain" or "dense")

    Returns:
        Dictionary with lists of metric values
    """
    from .graph import build_graph

    metrics = {
        "aspl": [],
        "mlcc": [],
        "modularity": [],
        "num_components": [],
        "avg_component_size": [],
        "gcc_size": [],
    }

    for concepts in woseco_lists:
        graph = build_graph(concepts, graph_type)
        story_metrics = calculate_all_metrics(graph)

        for key in metrics:
            metrics[key].append(story_metrics[key])

    return metrics


def aggregate_metrics(
    metrics_list: List[Dict[str, float]],
    aggregation: str = "mean"
) -> Dict[str, float]:
    """
    Aggregate metrics across multiple graphs.

    Args:
        metrics_list: List of metric dictionaries
        aggregation: Aggregation method ("mean", "median", "std")

    Returns:
        Aggregated metrics
    """
    if not metrics_list:
        return {}

    result = {}
    metric_names = metrics_list[0].keys()

    for metric_name in metric_names:
        values = [m[metric_name] for m in metrics_list]

        if aggregation == "mean":
            result[metric_name] = np.mean(values)
        elif aggregation == "median":
            result[metric_name] = np.median(values)
        elif aggregation == "std":
            result[metric_name] = np.std(values)
        else:
            raise ValueError(f"Unknown aggregation method: {aggregation}")

    return result


def compare_metrics(
    metrics_dict: Dict[str, List[float]],
    statistical_test: str = "kruskal"
) -> Dict[str, float]:
    """
    Perform statistical comparison of metrics across groups.

    Args:
        metrics_dict: Dictionary mapping group names to lists of metric values
        statistical_test: Statistical test to use ("kruskal", "anova")

    Returns:
        Dictionary with p-values for each metric
    """
    from scipy import stats as scipy_stats

    if statistical_test == "kruskal":
        # Kruskal-Wallis H-test (non-parametric)
        test_func = scipy_stats.kruskal
    elif statistical_test == "anova":
        # One-way ANOVA
        test_func = scipy_stats.f_oneway
    else:
        raise ValueError(f"Unknown statistical test: {statistical_test}")

    # Get all metric names from first group
    first_group = list(metrics_dict.values())[0]
    if isinstance(first_group, dict):
        metric_names = first_group.keys()
    else:
        return {}

    p_values = {}

    for metric_name in metric_names:
        # Extract values for this metric from all groups
        group_values = []
        for group_metrics in metrics_dict.values():
            if isinstance(group_metrics, dict):
                group_values.append(group_metrics[metric_name])
            elif isinstance(group_metrics, list):
                # Assume it's already a list of values
                group_values.append(group_metrics)

        # Perform test
        if len(group_values) >= 2:
            _, p_value = test_func(*group_values)
            p_values[metric_name] = p_value

    return p_values
