from functools import partial

import pytest

import networkx as nx
from networkx.algorithms.link_analysis.hits_alg import (
    _hits_numpy,
    _hits_python,
    _hits_svd,
)

np = pytest.importorskip("numpy")
sp = pytest.importorskip("scipy")

# Example from
# A. Langville and C. Meyer, "A survey of eigenvector methods of web
# information retrieval."  http://citeseer.ist.psu.edu/713792.html


class TestHITS:
    @classmethod
    def setup_class(cls):
        G = nx.DiGraph()

        edges = [(1, 3), (1, 5), (2, 1), (3, 5), (5, 4), (5, 3), (6, 5)]

        G.add_edges_from(edges, weight=1)
        cls.G = G
        cls.G.a = dict(
            zip(sorted(G), [0.000000, 0.000000, 0.366025, 0.133975, 0.500000, 0.000000])
        )
        cls.G.h = dict(
            zip(sorted(G), [0.366025, 0.000000, 0.211325, 0.000000, 0.211325, 0.211325])
        )

    def test_hits_numpy(self):
        G = self.G
        h, a = _hits_numpy(G)
        for n in G:
            assert h[n] == pytest.approx(G.h[n], abs=1e-4)
        for n in G:
            assert a[n] == pytest.approx(G.a[n], abs=1e-4)

    def test_hits_numpy_normalized_false_finite(self):
        # Regression for gh-8898: eigh can return a negated dominant
        # eigenvector; scaling by max() must not produce inf/NaN.
        G = nx.path_graph(3)
        hubs, authorities = _hits_numpy(G, normalized=False)
        assert all(np.isfinite(v) for v in hubs.values())
        assert all(np.isfinite(v) for v in authorities.values())
        assert all(v >= 0 for v in hubs.values())
        assert all(v >= 0 for v in authorities.values())

    @pytest.mark.parametrize(
        "hits_alg",
        (nx.hits, partial(nx.hits, method="svd"), _hits_python, _hits_svd),
    )
    def test_hits(self, hits_alg):
        G = self.G
        h, a = hits_alg(G, tol=1.0e-08)
        for n in G:
            assert h[n] == pytest.approx(G.h[n], abs=1e-4)
        for n in G:
            assert a[n] == pytest.approx(G.a[n], abs=1e-4)
        nstart = {i: 1.0 / 2 for i in G}
        h, a = hits_alg(G, nstart=nstart)
        for n in G:
            assert h[n] == pytest.approx(G.h[n], abs=1e-4)
        for n in G:
            assert a[n] == pytest.approx(G.a[n], abs=1e-4)

    def test_empty(self):
        G = nx.Graph()
        assert nx.hits(G) == ({}, {})
        assert _hits_numpy(G) == ({}, {})
        assert _hits_python(G) == ({}, {})

    def test_hits_not_convergent(self):
        G = nx.path_graph(50)
        with pytest.raises(nx.PowerIterationFailedConvergence):
            _hits_python(G, max_iter=1)
        with pytest.raises(nx.PowerIterationFailedConvergence):
            _hits_python(G, max_iter=0)
        with pytest.raises(nx.PowerIterationFailedConvergence):
            nx.hits(G, max_iter=0)
        with pytest.raises(nx.PowerIterationFailedConvergence):
            nx.hits(G, max_iter=1)

    @pytest.mark.parametrize("hits_alg", (nx.hits, _hits_python))
    def test_hits_gh6289_star_graph(self, hits_alg):
        G = nx.star_graph(9)
        h, a = hits_alg(G)
        assert all(v >= 0 for v in h.values())
        assert all(v >= 0 for v in a.values())

    def test_hits_invalid_method(self):
        with pytest.raises(ValueError, match="method not supported"):
            nx.hits(self.G, method="spam")
