"""
Tests for wosecopy.metrics module.
"""
import pytest
import networkx as nx
from wosecopy.graph import build_graph
from wosecopy.metrics import (
    calculate_aspl,
    calculate_mlcc,
    calculate_modularity,
    calculate_num_components,
    calculate_avg_component_size,
    calculate_gcc_size,
    calculate_all_metrics,
    stats,
    aggregate_metrics,
)


class TestASPL:
    """Test Average Shortest Path Length calculation."""

    def test_aspl_empty_graph(self):
        """Test ASPL on empty graph."""
        graph = nx.Graph()
        aspl = calculate_aspl(graph)
        assert aspl == 0.0

    def test_aspl_connected_graph(self):
        """Test ASPL on connected graph."""
        graph = build_graph(['a', 'b', 'c', 'd'], graph_type='chain')
        aspl = calculate_aspl(graph)
        assert aspl > 0
        assert isinstance(aspl, float)

    def test_aspl_disconnected_graph(self):
        """Test ASPL on disconnected graph uses largest component."""
        graph = nx.Graph()
        graph.add_edge('a', 'b')
        graph.add_edge('c', 'd')
        aspl = calculate_aspl(graph)
        # Should calculate for largest component only
        assert aspl > 0 or aspl == 0


class TestMLCC:
    """Test Mean Local Clustering Coefficient calculation."""

    def test_mlcc_empty_graph(self):
        """Test MLCC on empty graph."""
        graph = nx.Graph()
        mlcc = calculate_mlcc(graph)
        assert mlcc == 0.0

    def test_mlcc_chain_graph(self):
        """Test MLCC on chain graph."""
        graph = build_graph(['a', 'b', 'c'], graph_type='chain')
        mlcc = calculate_mlcc(graph)
        assert 0.0 <= mlcc <= 1.0

    def test_mlcc_dense_graph(self):
        """Test MLCC on dense graph."""
        graph = build_graph(['a', 'b', 'c'], graph_type='dense')
        mlcc = calculate_mlcc(graph)
        # Complete triangle should have clustering of 1.0
        assert mlcc == 1.0


class TestModularity:
    """Test Modularity calculation."""

    def test_modularity_empty_graph(self):
        """Test modularity on empty graph."""
        graph = nx.Graph()
        mod = calculate_modularity(graph)
        assert mod == 0.0

    def test_modularity_single_component(self):
        """Test modularity on single component graph."""
        graph = build_graph(['a', 'b', 'c'], graph_type='chain')
        mod = calculate_modularity(graph)
        assert isinstance(mod, float)


class TestComponentMetrics:
    """Test component-related metrics."""

    def test_num_components_empty(self):
        """Test component counting on empty graph."""
        graph = nx.Graph()
        num = calculate_num_components(graph)
        assert num == 0

    def test_num_components_connected(self):
        """Test component counting on connected graph."""
        graph = build_graph(['a', 'b', 'c'], graph_type='chain')
        num = calculate_num_components(graph)
        assert num == 1

    def test_num_components_disconnected(self):
        """Test component counting on disconnected graph."""
        graph = nx.Graph()
        graph.add_edge('a', 'b')
        graph.add_edge('c', 'd')
        num = calculate_num_components(graph)
        assert num == 2

    def test_avg_component_size_empty(self):
        """Test average component size on empty graph."""
        graph = nx.Graph()
        avg_size = calculate_avg_component_size(graph)
        assert avg_size == 0.0

    def test_avg_component_size(self):
        """Test average component size calculation."""
        graph = nx.Graph()
        graph.add_edge('a', 'b')
        graph.add_edge('c', 'd')
        graph.add_edge('e', 'f')
        avg_size = calculate_avg_component_size(graph)
        assert avg_size == 2.0  # Three components of size 2

    def test_gcc_size_empty(self):
        """Test GCC size on empty graph."""
        graph = nx.Graph()
        gcc_size = calculate_gcc_size(graph)
        assert gcc_size == 0

    def test_gcc_size_single_component(self):
        """Test GCC size on single component."""
        graph = build_graph(['a', 'b', 'c'], graph_type='chain')
        gcc_size = calculate_gcc_size(graph)
        assert gcc_size == 3

    def test_gcc_size_multiple_components(self):
        """Test GCC size picks largest component."""
        graph = nx.Graph()
        graph.add_edge('a', 'b')
        graph.add_edge('c', 'd')
        graph.add_edge('d', 'e')
        gcc_size = calculate_gcc_size(graph)
        assert gcc_size == 3  # Largest component is c-d-e


class TestCalculateAllMetrics:
    """Test calculating all metrics at once."""

    def test_all_metrics_complete(self):
        """Test that all metrics are calculated."""
        graph = build_graph(['a', 'b', 'c', 'd'], graph_type='chain')
        metrics = calculate_all_metrics(graph)

        assert 'aspl' in metrics
        assert 'mlcc' in metrics
        assert 'modularity' in metrics
        assert 'num_components' in metrics
        assert 'avg_component_size' in metrics
        assert 'gcc_size' in metrics

    def test_all_metrics_types(self):
        """Test that metrics have correct types."""
        graph = build_graph(['a', 'b', 'c'], graph_type='chain')
        metrics = calculate_all_metrics(graph)

        assert isinstance(metrics['aspl'], (int, float))
        assert isinstance(metrics['mlcc'], (int, float))
        assert isinstance(metrics['modularity'], (int, float))
        assert isinstance(metrics['num_components'], int)
        assert isinstance(metrics['avg_component_size'], (int, float))
        assert isinstance(metrics['gcc_size'], int)


class TestStats:
    """Test the stats function (main metrics calculator)."""

    def test_stats_single_concept_list(self):
        """Test stats with single concept list."""
        concept_lists = [['a', 'b', 'c']]
        metrics = stats(concept_lists, graph_type='chain')

        assert len(metrics['aspl']) == 1
        assert len(metrics['mlcc']) == 1
        assert len(metrics['modularity']) == 1

    def test_stats_multiple_concept_lists(self):
        """Test stats with multiple concept lists."""
        concept_lists = [
            ['a', 'b', 'c'],
            ['x', 'y'],
            ['one', 'two', 'three', 'four']
        ]
        metrics = stats(concept_lists, graph_type='chain')

        assert len(metrics['aspl']) == 3
        assert len(metrics['mlcc']) == 3
        assert len(metrics['num_components']) == 3

    def test_stats_empty_list(self):
        """Test stats with empty list."""
        concept_lists = []
        metrics = stats(concept_lists)

        assert len(metrics['aspl']) == 0


class TestAggregateMetrics:
    """Test metrics aggregation."""

    def test_aggregate_mean(self):
        """Test mean aggregation."""
        metrics_list = [
            {'aspl': 2.0, 'mlcc': 0.5},
            {'aspl': 4.0, 'mlcc': 0.7},
        ]
        aggregated = aggregate_metrics(metrics_list, aggregation='mean')

        assert aggregated['aspl'] == 3.0
        assert aggregated['mlcc'] == 0.6

    def test_aggregate_median(self):
        """Test median aggregation."""
        metrics_list = [
            {'aspl': 1.0},
            {'aspl': 2.0},
            {'aspl': 3.0},
        ]
        aggregated = aggregate_metrics(metrics_list, aggregation='median')

        assert aggregated['aspl'] == 2.0

    def test_aggregate_std(self):
        """Test standard deviation aggregation."""
        metrics_list = [
            {'aspl': 2.0},
            {'aspl': 4.0},
        ]
        aggregated = aggregate_metrics(metrics_list, aggregation='std')

        assert 'aspl' in aggregated
        assert isinstance(aggregated['aspl'], float)

    def test_aggregate_empty_list(self):
        """Test aggregation with empty list."""
        metrics_list = []
        aggregated = aggregate_metrics(metrics_list)

        assert aggregated == {}

    def test_aggregate_invalid_method(self):
        """Test that invalid aggregation method raises error."""
        metrics_list = [{'aspl': 2.0}]

        with pytest.raises(ValueError):
            aggregate_metrics(metrics_list, aggregation='invalid')
