"""Tests for negated edge conditions: WHERE NOT (a)-->(b)"""

import networkx as nx
import pytest

from . import NXCypher

ACCEPTED_GRAPH_TYPES = [nx.MultiDiGraph, nx.DiGraph]


class TestNegatedEdges:
    """Test negated edge pattern conditions."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_simple_directed(self, graph_type):
        """Test that NOT (a)-->(b) finds nodes without direct edge."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_node("d", name="Diana")
        G.add_edge("a", "b")  # a -> b exists
        G.add_edge("a", "c")  # a -> c exists
        # No edge from a to d

        # Find nodes that a does NOT have an edge to
        # Use multiple MATCH clauses since MATCH (x), (y) isn't supported
        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)-->(y)
            RETURN y.name
        """)

        # a has no edge to d, and no edge to itself
        assert "Diana" in result["y.name"]
        assert "Alice" in result["y.name"]  # No self-loop
        assert "Bob" not in result["y.name"]
        assert "Charlie" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_edge_exists_simple(self, graph_type):
        """Test that (a)-->(b) finds nodes with direct edge."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_node("d", name="Diana")
        G.add_edge("a", "b")
        G.add_edge("a", "c")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE (x)-->(y)
            RETURN y.name
        """)

        assert "Bob" in result["y.name"]
        assert "Charlie" in result["y.name"]
        assert "Diana" not in result["y.name"]
        assert "Alice" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_left_direction(self, graph_type):
        """Test NOT (a)<--(b) pattern."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_edge("b", "a")  # b -> a exists
        # No edge from c to a

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)<--(y)
            RETURN y.name
        """)

        # Alice, Charlie have no edge TO Alice (in reverse direction)
        assert "Charlie" in result["y.name"]
        assert "Alice" in result["y.name"]
        assert "Bob" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_undirected(self, graph_type):
        """Test NOT (a)--(b) pattern (checks both directions)."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_node("d", name="Diana")
        G.add_edge("a", "b")  # a -> b
        G.add_edge("c", "a")  # c -> a

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)--(y)
            RETURN y.name
        """)

        # Only Diana and Alice have no edge with Alice (in either direction)
        assert "Diana" in result["y.name"]
        assert "Alice" in result["y.name"]
        assert "Bob" not in result["y.name"]
        assert "Charlie" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_with_type(self, graph_type):
        """Test NOT (a)-[:TYPE]->(b) pattern with edge type."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_edge("a", "b", __labels__={"KNOWS"})
        G.add_edge("a", "c", __labels__={"WORKS_WITH"})

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)-[:KNOWS]->(y)
            RETURN y.name
        """)

        # Alice has no KNOWS edge to Charlie, or Alice
        assert "Charlie" in result["y.name"]
        assert "Alice" in result["y.name"]
        assert "Bob" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_edge_exists_with_type(self, graph_type):
        """Test (a)-[:TYPE]->(b) pattern with edge type."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_edge("a", "b", __labels__={"KNOWS"})
        G.add_edge("a", "c", __labels__={"WORKS_WITH"})

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE (x)-[:KNOWS]->(y)
            RETURN y.name
        """)

        assert result["y.name"] == ["Bob"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_combined_with_and(self, graph_type):
        """Test combining negated edge with other conditions using AND."""
        G = graph_type()
        G.add_node("a", name="Alice", age=30)
        G.add_node("b", name="Bob", age=25)
        G.add_node("c", name="Charlie", age=35)
        G.add_node("d", name="Diana", age=28)
        G.add_edge("a", "b")
        G.add_edge("a", "c")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)-->(y) AND y.age > 25
            RETURN y.name
        """)

        # Diana has age > 25 and no edge from Alice
        # Alice has age > 25 but is self
        assert "Diana" in result["y.name"]
        assert "Alice" in result["y.name"]
        assert "Bob" not in result["y.name"]
        assert "Charlie" not in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_edge_exists_combined_with_and(self, graph_type):
        """Test combining edge exists with other conditions using AND."""
        G = graph_type()
        G.add_node("a", name="Alice", age=30)
        G.add_node("b", name="Bob", age=25)
        G.add_node("c", name="Charlie", age=35)
        G.add_edge("a", "b")
        G.add_edge("a", "c")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE (x)-->(y) AND y.age > 30
            RETURN y.name
        """)

        # Only Charlie has age > 30 and has edge from Alice
        assert result["y.name"] == ["Charlie"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_negated_edge_in_or_condition(self, graph_type):
        """Test negated edge in OR condition."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_edge("a", "b")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)-->(y) OR y.name = "Bob"
            RETURN y.name
        """)

        # Bob (has edge but matches OR), Charlie (no edge), Alice (no self-loop)
        assert "Bob" in result["y.name"]
        assert "Charlie" in result["y.name"]
        assert "Alice" in result["y.name"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_multiple_negated_edges(self, graph_type):
        """Test multiple negated edge conditions."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_node("d", name="Diana")
        G.add_edge("a", "b")
        G.add_edge("b", "c")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y {name: "Bob"})
            MATCH (z)
            WHERE NOT (x)-->(z) AND NOT (y)-->(z)
            RETURN z.name
        """)

        # Alice has edge to Bob, Bob has edge to Charlie
        # Diana has no incoming edges from either
        assert "Diana" in result["z.name"]


class TestEdgePatternSyntax:
    """Test various edge pattern syntaxes."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_bracket_syntax_directed(self, graph_type):
        """Test -[]-> syntax for edge existence."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_edge("a", "b")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE (x)-[]->(y)
            RETURN y.name
        """)

        assert result["y.name"] == ["Bob"]

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_bracket_syntax_negated(self, graph_type):
        """Test NOT -[]-> syntax."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_node("c", name="Charlie")
        G.add_edge("a", "b")

        result = NXCypher(G).run("""
            MATCH (x {name: "Alice"})
            MATCH (y)
            WHERE NOT (x)-[]->(y)
            RETURN y.name
        """)

        assert "Charlie" in result["y.name"]
        assert "Alice" in result["y.name"]
        assert "Bob" not in result["y.name"]
