import pytest
import networkx as nx
from well_routing.classes import Node, GraphNode, Edge, Graph


class TestNode:
    def test_node_creation(self):
        node = Node("TEST-NODE", "well")
        assert node.uoi == "TEST-NODE"
        assert node.node_type == "well"

    def test_node_name_property(self):
        node = Node("TEST-NODE")
        assert node.name == "TEST-NODE"

    def test_node_repr(self):
        node = Node("TEST-NODE", "well")
        assert repr(node) == "Node(TEST-NODE, type=well)"

    def test_node_empty_type(self):
        node = Node("TEST-NODE")
        assert node.node_type == ""


class TestGraphNode:
    def test_graphnode_creation(self):
        node = GraphNode("TEST-NODE", "well", {"key": "value"})
        assert node.uoi == "TEST-NODE"
        assert node.node_type == "well"
        assert node.properties == {"key": "value"}

    def test_graphnode_empty_properties(self):
        node = GraphNode("TEST-NODE", "well")
        assert node.properties == {}

    def test_graphnode_repr(self):
        node = GraphNode("TEST-NODE", "well", {"pressure": 100})
        assert "TEST-NODE" in repr(node)
        assert "pressure" in repr(node)


class TestEdge:
    def test_edge_creation(self):
        edge = Edge("NODE-A", "NODE-B", weight=1.5, tag_no="TAG001", tag_source="ots")
        assert edge.from_node == "NODE-A"
        assert edge.to_node == "NODE-B"
        assert edge.weight == 1.5
        assert edge.tag_no == "TAG001"
        assert edge.tag_source == "ots"

    def test_edge_default_weight(self):
        edge = Edge("NODE-A", "NODE-B")
        assert edge.weight == float("Inf")

    def test_edge_repr(self):
        edge = Edge("NODE-A", "NODE-B", weight=2.0)
        assert repr(edge) == "Edge(from=NODE-A, to=NODE-B, weight=2.0)"


class TestGraph:
    @pytest.fixture(scope="class")
    def sample_graph(self):
        graph = Graph("test-graph")
        graph.add_node(Node("WELL-1", "well"))
        graph.add_node(Node("MANIFOLD-1", "manifold"))
        graph.add_node(Node("INLET-1", "inlet"))
        return graph

    @pytest.fixture(scope="class")
    def connected_graph(self, sample_graph):
        edge1 = Edge("WELL-1", "MANIFOLD-1", weight=1.0)
        edge2 = Edge("MANIFOLD-1", "INLET-1", weight=1.0)
        sample_graph.add_edge(edge1)
        sample_graph.add_edge(edge2)
        return sample_graph

    def test_graph_creation(self):
        graph = Graph("test-graph")
        assert graph.name == "test-graph"
        assert graph.nodes == []
        assert graph.edges == []

    def test_add_node(self, sample_graph):
        assert len(sample_graph.nodes) == 3

    def test_has_node(self, sample_graph):
        assert sample_graph.has_node("WELL-1")
        assert sample_graph.has_node("well-1")  # case insensitive
        assert not sample_graph.has_node("NONEXISTENT")

    def test_get_node(self, sample_graph):
        node = sample_graph.get_node("WELL-1")
        assert node is not None
        assert node.uoi == "WELL-1"

    def test_get_node_case_insensitive(self, sample_graph):
        node = sample_graph.get_node("well-1")
        assert node is not None
        assert node.uoi == "WELL-1"

    def test_get_node_wildcard(self, sample_graph):
        node = sample_graph.get_node("WELL*")
        assert node is not None
        assert node.uoi == "WELL-1"

    def test_rm_node(self, sample_graph):
        sample_graph.rm_node("WELL-1")
        assert not sample_graph.has_node("WELL-1")
        assert len(sample_graph.nodes) == 2
        sample_graph.add_node(Node("WELL-1", "well"))  # Re-add for other tests

    def test_get_nodes_by_type(self, sample_graph):
        wells = sample_graph.get_nodes("well")
        assert len(wells) == 1
        assert wells[0].uoi == "WELL-1"

    def test_get_nodes_empty_type(self, sample_graph):
        all_nodes = sample_graph.get_nodes("")
        assert len(all_nodes) == 3

    def test_add_edge(self, sample_graph):
        edge = Edge("WELL-1", "MANIFOLD-1", weight=1.0)
        sample_graph.add_edge(edge)
        assert len(sample_graph.edges) == 1

    def test_has_edge(self, sample_graph):
        edge = Edge("WELL-1", "MANIFOLD-1", weight=1.0)
        sample_graph.add_edge(edge)
        assert sample_graph.has_edge(edge)

    def test_rm_edge(self, sample_graph):
        edge = Edge("WELL-1", "MANIFOLD-1", weight=1.0)
        sample_graph.add_edge(edge)
        sample_graph.rm_edge(edge)
        assert len(sample_graph.edges) == 0

    def test_get_islands(self, sample_graph):
        islands = sample_graph.get_islands()
        assert len(islands) == 3  # No edges, all are islands

    def test_get_islands_with_edges(self, connected_graph):
        islands = connected_graph.get_islands()
        assert len(islands) == 0  # All nodes are connected

    def test_get_sources(self, connected_graph):
        sources = connected_graph.get_sources()
        assert len(sources) == 1
        assert sources[0].uoi == "WELL-1"

    def test_get_sinks(self, connected_graph):
        sinks = connected_graph.get_sinks()
        assert len(sinks) == 1
        assert sinks[0].uoi == "INLET-1"

    def test_convert_to_networkx_directed(self, connected_graph):
        G = connected_graph.convert_to_networkx(directed=True)
        assert isinstance(G, nx.DiGraph)
        assert len(G.nodes()) == 3
        assert len(G.edges()) == 2

    def test_convert_to_networkx_undirected(self, connected_graph):
        G = connected_graph.convert_to_networkx(directed=False)
        assert isinstance(G, nx.Graph)
        assert len(G.nodes()) == 3
        assert len(G.edges()) == 2

    def test_has_path(self, connected_graph):
        assert connected_graph.has_path("WELL-1", "INLET-1")
        assert not connected_graph.has_path("INLET-1", "WELL-1")

    def test_shortest_path(self, connected_graph):
        path = connected_graph.shortest_path("WELL-1", "INLET-1")
        assert path == ["WELL-1", "MANIFOLD-1", "INLET-1"]

    def test_path_weight_no_timestamp(self, connected_graph):
        path = ["WELL-1", "MANIFOLD-1", "INLET-1"]
        weight = connected_graph.path_weight(path)
        assert weight == 2  # len(path) - 1

    def test_graph_str(self, connected_graph):
        assert "3 nodes" in str(connected_graph)
        assert "2 edges" in str(connected_graph)
