from unittest.mock import Mock, patch

from networkx import DiGraph

from django.test import SimpleTestCase

from routing.graph import build


class GraphBuildTest(SimpleTestCase):
    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_build_dynamic_graph(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        mock_nodes.return_value = [(1, 0.5), (2, 0.4)]
        mock_edges.return_value.all.return_value = [(1, 2, 1.0, 2.0, 3.0)]

        graph = build(static_cache=False)

        self.assertIsInstance(graph, DiGraph)
        self.assertIn(1, graph.nodes)
        self.assertIn(2, graph.nodes)
        self.assertTrue(graph.has_edge(1, 2))
        self.assertEqual(graph[1][2]["p_shortest"], 1.0)
        self.assertEqual(graph[1][2]["p_safest"], 2.0)
        self.assertEqual(graph[1][2]["p_less_safe"], 3.0)
        self.assertEqual(graph[1][2]["type"], "Stargate")

    @patch("routing.graph.precomputed_graph", {1: {2: {"p_shortest": 1, "type": "Stargate"}}})
    def test_build_static_graph(self) -> None:
        graph = build(static_cache=True)
        self.assertIsInstance(graph, DiGraph)
        self.assertTrue(graph.has_edge(1, 2))


class GraphIntegrationTest(SimpleTestCase):
    """Integration tests for graph building"""

    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_graph_is_directed(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        """Verify graph is directed (edges only go one way)"""
        mock_nodes.return_value = [(1, 0.5), (2, 0.4)]
        mock_edges.return_value.all.return_value = [(1, 2, 1.0, 2.0, 3.0)]

        graph = build(static_cache=False)

        # Edge should exist from 1 to 2
        self.assertTrue(graph.has_edge(1, 2))
        # But not the reverse
        self.assertFalse(graph.has_edge(2, 1))

    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_graph_nodes_have_security_attribute(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        """Verify nodes include security status attribute"""
        mock_nodes.return_value = [(1, 0.5), (2, 0.4)]
        mock_edges.return_value.all.return_value = []

        graph = build(static_cache=False)

        self.assertEqual(graph.nodes[1]["security"], 0.5)
        self.assertEqual(graph.nodes[2]["security"], 0.4)
        self.assertEqual(graph.nodes[1]["type"], "solar_system")

    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_build_empty_graph(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        """An empty SDE produces a valid empty DiGraph"""
        mock_nodes.return_value = []
        mock_edges.return_value.all.return_value = []

        graph = build(static_cache=False)

        self.assertIsInstance(graph, DiGraph)
        self.assertEqual(len(graph.nodes), 0)
        self.assertEqual(len(graph.edges), 0)

    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_build_node_and_edge_counts(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        """Node and edge counts match the mocked SDE data exactly"""
        mock_nodes.return_value = [(1, 0.5), (2, 0.4), (3, 0.1)]
        mock_edges.return_value.all.return_value = [(1, 2, 1.0, 2.0, 3.0), (2, 3, 1.0, 2.0, 3.0)]

        graph = build(static_cache=False)

        self.assertEqual(len(graph.nodes), 3)
        self.assertEqual(len(graph.edges), 2)

    @patch("routing.graph.SolarSystemConnection.objects.values_list")
    @patch("routing.graph.SolarSystem.objects.values_list")
    def test_static_cache_does_not_call_orm(self, mock_nodes: Mock, mock_edges: Mock) -> None:
        """Static cache path must never touch the ORM"""
        with patch("routing.graph.precomputed_graph", {1: {2: {"p_shortest": 1, "type": "Stargate"}}}):
            build(static_cache=True)

        mock_nodes.assert_not_called()
        mock_edges.assert_not_called()
        mock_edges.assert_not_called()
