"""Tests for graph mutation operations: CREATE, SET, DELETE."""

import networkx as nx
import pytest

from . import NXCypher

ACCEPTED_GRAPH_TYPES = [nx.MultiDiGraph, nx.DiGraph]


class TestCreateNode:
    """Test CREATE node functionality."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_node_simple(self, graph_type):
        """Test creating a simple node with properties."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE (n {name: "Alice", age: 30})
        """)

        # Find the created node
        nodes = [n for n, d in G.nodes(data=True) if d.get("name") == "Alice"]
        assert len(nodes) == 1
        assert G.nodes[nodes[0]]["age"] == 30

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_node_with_label(self, graph_type):
        """Test creating a node with a label."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE (n:Person {name: "Bob"})
        """)

        nodes = [n for n, d in G.nodes(data=True) if d.get("name") == "Bob"]
        assert len(nodes) == 1
        assert "Person" in G.nodes[nodes[0]].get("__labels__", set())

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_multiple_nodes(self, graph_type):
        """Test creating multiple nodes in one CREATE."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE (a {name: "Alice"}), (b {name: "Bob"})
        """)

        alice_nodes = [n for n, d in G.nodes(data=True) if d.get("name") == "Alice"]
        bob_nodes = [n for n, d in G.nodes(data=True) if d.get("name") == "Bob"]
        assert len(alice_nodes) == 1
        assert len(bob_nodes) == 1

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_node_no_alias(self, graph_type):
        """Test creating a node without an alias."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE ({name: "Anonymous"})
        """)

        nodes = [n for n, d in G.nodes(data=True) if d.get("name") == "Anonymous"]
        assert len(nodes) == 1


class TestCreateRelationship:
    """Test CREATE relationship functionality."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_relationship_between_new_nodes(self, graph_type):
        """Test creating an edge between two new nodes."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE (a {name: "Alice"})-[:KNOWS]->(b {name: "Bob"})
        """)

        # Find Alice and Bob
        alice = [n for n, d in G.nodes(data=True) if d.get("name") == "Alice"][0]
        bob = [n for n, d in G.nodes(data=True) if d.get("name") == "Bob"][0]

        assert G.has_edge(alice, bob)
        edge_data = G.get_edge_data(alice, bob)
        if graph_type == nx.MultiDiGraph:
            edge_data = list(edge_data.values())[0]
        assert "KNOWS" in edge_data.get("__labels__", set())

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_relationship_with_properties(self, graph_type):
        """Test creating an edge with properties."""
        G = graph_type()

        NXCypher(G).run("""
            CREATE (a {name: "Alice"})-[:KNOWS {since: 2020}]->(b {name: "Bob"})
        """)

        alice = [n for n, d in G.nodes(data=True) if d.get("name") == "Alice"][0]
        bob = [n for n, d in G.nodes(data=True) if d.get("name") == "Bob"][0]

        edge_data = G.get_edge_data(alice, bob)
        if graph_type == nx.MultiDiGraph:
            edge_data = list(edge_data.values())[0]
        assert edge_data["since"] == 2020

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_relationship_between_matched_nodes(self, graph_type):
        """Test creating an edge between existing matched nodes."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")

        NXCypher(G).run("""
            MATCH (a {name: "Alice"})
            MATCH (b {name: "Bob"})
            CREATE (a)-[:KNOWS {since: 2020}]->(b)
        """)

        assert G.has_edge("a", "b")
        edge_data = G.get_edge_data("a", "b")
        if graph_type == nx.MultiDiGraph:
            edge_data = list(edge_data.values())[0]
        assert edge_data["since"] == 2020


class TestSetProperty:
    """Test SET property functionality."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_set_property_on_node(self, graph_type):
        """Test setting a property on a matched node."""
        G = graph_type()
        G.add_node("a", name="Alice", age=30)

        NXCypher(G).run("""
            MATCH (n {name: "Alice"})
            SET n.age = 31
        """)

        assert G.nodes["a"]["age"] == 31

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_set_new_property(self, graph_type):
        """Test setting a new property on a node."""
        G = graph_type()
        G.add_node("a", name="Alice")

        NXCypher(G).run("""
            MATCH (n {name: "Alice"})
            SET n.city = "NYC"
        """)

        assert G.nodes["a"]["city"] == "NYC"

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_set_multiple_properties(self, graph_type):
        """Test setting multiple properties."""
        G = graph_type()
        G.add_node("a", name="Alice")

        NXCypher(G).run("""
            MATCH (n {name: "Alice"})
            SET n.age = 30, n.city = "NYC"
        """)

        assert G.nodes["a"]["age"] == 30
        assert G.nodes["a"]["city"] == "NYC"


class TestDelete:
    """Test DELETE functionality."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_delete_node_no_edges(self, graph_type):
        """Test deleting a node with no edges."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")

        NXCypher(G).run("""
            MATCH (n {name: "Bob"})
            DELETE n
        """)

        assert "a" in G.nodes
        assert "b" not in G.nodes

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_delete_node_with_edges_fails(self, graph_type):
        """Test that deleting a node with edges raises an error."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_edge("a", "b")

        with pytest.raises(ValueError, match="Cannot delete node"):
            NXCypher(G).run("""
                MATCH (n {name: "Bob"})
                DELETE n
            """)

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_detach_delete(self, graph_type):
        """Test DETACH DELETE removes node and its edges."""
        G = graph_type()
        G.add_node("a", name="Alice")
        G.add_node("b", name="Bob")
        G.add_edge("a", "b")

        NXCypher(G).run("""
            MATCH (n {name: "Bob"})
            DETACH DELETE n
        """)

        assert "a" in G.nodes
        assert "b" not in G.nodes
        assert not G.has_edge("a", "b")


class TestMutationResults:
    """Test mutation return values."""

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_create_returns_results(self, graph_type):
        """Test that CREATE returns mutation results."""
        G = graph_type()

        result = NXCypher(G).run("""
            CREATE (n {name: "Alice"})
        """)

        assert "created_nodes" in result
        assert len(result["created_nodes"]) == 1

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_set_returns_results(self, graph_type):
        """Test that SET returns mutation results."""
        G = graph_type()
        G.add_node("a", name="Alice")

        result = NXCypher(G).run("""
            MATCH (n {name: "Alice"})
            SET n.age = 30
        """)

        assert "updated" in result
        assert len(result["updated"]) >= 1

    @pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
    def test_delete_returns_results(self, graph_type):
        """Test that DELETE returns mutation results."""
        G = graph_type()
        G.add_node("a", name="Alice")

        result = NXCypher(G).run("""
            MATCH (n {name: "Alice"})
            DELETE n
        """)

        assert "deleted" in result
        assert len(result["deleted"]) == 1
