"""
Tests for scene manipulation operations: node_set_property, node_delete,
scene_instantiate, node_rename, node_reparent, node_duplicate, node_add_to_group.

These tests verify the Phase 1 tools that enable full control
over scene graph structure and node properties.
"""

import json
from pathlib import Path

from godot_mcp.server import (
    node_add,
    node_add_to_group,
    scene_create,
    node_delete,
    node_duplicate,
    scene_get_tree,
    scene_instantiate,
    node_reparent,
    node_rename,
    node_set_property,
)


class TestSetNodeProperty:
    """Test cases for the node_set_property tool."""

    def test_set_simple_property(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test setting a simple property like visible."""
        result = node_set_property(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root",
            property_name="visible",
            property_value=False,
        )

        assert "successfully" in result.lower()

    def test_set_vector2_property(self, temp_godot_project: Path) -> None:
        """Test setting a Vector2 property using dict."""
        # Create a scene with a Sprite2D
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="vector_test.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="vector_test.tscn",
            node_type="Sprite2D",
            node_name="Sprite",
        )

        result = node_set_property(
            project_path=str(temp_godot_project),
            scene_path="vector_test.tscn",
            node_path="root/Sprite",
            property_name="position",
            property_value={"x": 100, "y": 200},
        )

        assert "successfully" in result.lower()

    def test_set_property_invalid_node(
        self, temp_godot_project: Path, temp_scene_file: Path
    ) -> None:
        """Test error handling for non-existent node."""
        result = node_set_property(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root/NonExistent",
            property_name="visible",
            property_value=True,
        )

        assert "failed" in result.lower() or "not found" in result.lower()

    def test_set_property_missing_params(self) -> None:
        """Test error handling for missing parameters."""
        result = node_set_property(
            project_path="",
            scene_path="",
            node_path="",
            property_name="",
            property_value=None,
        )

        assert "missing" in result.lower() or "required" in result.lower()


class TestDeleteNode:
    """Test cases for the node_delete tool."""

    def test_delete_child_node(self, temp_godot_project: Path) -> None:
        """Test deleting a child node."""
        # Create scene with a child node
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="delete_test.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="delete_test.tscn",
            node_type="Sprite2D",
            node_name="ToDelete",
        )

        # Delete the node
        result = node_delete(
            project_path=str(temp_godot_project),
            scene_path="delete_test.tscn",
            node_path="root/ToDelete",
        )

        assert "deleted" in result.lower() or "successfully" in result.lower()

        # Verify node is gone
        tree = scene_get_tree(
            project_path=str(temp_godot_project),
            scene_path="delete_test.tscn",
        )
        data = json.loads(tree)
        assert "children" not in data or len(data.get("children", [])) == 0

    def test_delete_root_node_fails(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test that deleting root node fails."""
        result = node_delete(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root",
        )

        assert "cannot" in result.lower() or "error" in result.lower()

    def test_delete_invalid_node(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test error handling for non-existent node."""
        result = node_delete(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root/NonExistent",
        )

        assert "failed" in result.lower() or "not found" in result.lower()


class TestInstanceScene:
    """Test cases for the scene_instantiate tool."""

    def test_scene_instantiate_basic(self, temp_godot_project: Path) -> None:
        """Test instancing one scene into another."""
        # Create source scene
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="source.tscn",
            root_node_type="Node2D",
        )

        # Create target scene
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="target.tscn",
            root_node_type="Node2D",
        )

        # Instance source into target
        result = scene_instantiate(
            project_path=str(temp_godot_project),
            target_scene_path="target.tscn",
            source_scene_path="source.tscn",
        )

        assert "instanced" in result.lower() or "successfully" in result.lower()

    def test_scene_instantiate_with_name(self, temp_godot_project: Path) -> None:
        """Test instancing with a custom name."""
        # Create source scene
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="player.tscn",
            root_node_type="CharacterBody2D",
        )

        # Create main scene
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="main.tscn",
            root_node_type="Node2D",
        )

        result = scene_instantiate(
            project_path=str(temp_godot_project),
            target_scene_path="main.tscn",
            source_scene_path="player.tscn",
            instance_name="Player1",
        )

        assert "Player1" in result

    def test_instance_invalid_source(self, temp_godot_project: Path) -> None:
        """Test error handling for non-existent source scene."""
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="target2.tscn",
            root_node_type="Node2D",
        )

        result = scene_instantiate(
            project_path=str(temp_godot_project),
            target_scene_path="target2.tscn",
            source_scene_path="nonexistent.tscn",
        )

        assert "does not exist" in result or "error" in result.lower()


class TestRenameNode:
    """Test cases for the node_rename tool."""

    def test_node_rename_basic(self, temp_godot_project: Path) -> None:
        """Test renaming a node."""
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="rename_test.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="rename_test.tscn",
            node_type="Sprite2D",
            node_name="OldName",
        )

        result = node_rename(
            project_path=str(temp_godot_project),
            scene_path="rename_test.tscn",
            node_path="root/OldName",
            new_name="NewName",
        )

        assert "renamed" in result.lower() or "successfully" in result.lower()

        # Verify the rename worked
        tree = scene_get_tree(
            project_path=str(temp_godot_project),
            scene_path="rename_test.tscn",
        )
        data = json.loads(tree)
        child_names = [c["name"] for c in data.get("children", [])]
        assert "NewName" in child_names
        assert "OldName" not in child_names

    def test_rename_invalid_node(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test error handling for non-existent node."""
        result = node_rename(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root/NonExistent",
            new_name="NewName",
        )

        assert "failed" in result.lower() or "not found" in result.lower()


class TestMoveNode:
    """Test cases for the node_reparent tool."""

    def test_node_reparent_basic(self, temp_godot_project: Path) -> None:
        """Test moving a node to a new parent."""
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="move_test.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="move_test.tscn",
            node_type="Node2D",
            node_name="Container",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="move_test.tscn",
            node_type="Sprite2D",
            node_name="Sprite",
        )

        result = node_reparent(
            project_path=str(temp_godot_project),
            scene_path="move_test.tscn",
            node_path="root/Sprite",
            new_parent_path="root/Container",
        )

        assert "moved" in result.lower() or "successfully" in result.lower()

    def test_move_root_fails(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test that moving root node fails."""
        result = node_reparent(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root",
            new_parent_path="root",
        )

        assert "cannot" in result.lower() or "error" in result.lower()


class TestDuplicateNode:
    """Test cases for the node_duplicate tool."""

    def test_node_duplicate_basic(self, temp_godot_project: Path) -> None:
        """Test duplicating a node."""
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="dup_test.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="dup_test.tscn",
            node_type="Sprite2D",
            node_name="Original",
        )

        result = node_duplicate(
            project_path=str(temp_godot_project),
            scene_path="dup_test.tscn",
            node_path="root/Original",
        )

        assert "duplicated" in result.lower() or "successfully" in result.lower()

        # Verify both nodes exist
        tree = scene_get_tree(
            project_path=str(temp_godot_project),
            scene_path="dup_test.tscn",
        )
        data = json.loads(tree)
        assert len(data.get("children", [])) == 2

    def test_node_duplicate_with_name(self, temp_godot_project: Path) -> None:
        """Test duplicating with a custom name."""
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="dup_named.tscn",
            root_node_type="Node2D",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="dup_named.tscn",
            node_type="Sprite2D",
            node_name="Original",
        )

        result = node_duplicate(
            project_path=str(temp_godot_project),
            scene_path="dup_named.tscn",
            node_path="root/Original",
            new_name="Copy",
        )

        assert "Copy" in result

    def test_duplicate_root_fails(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test that duplicating root node fails."""
        result = node_duplicate(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root",
        )

        assert "cannot" in result.lower() or "error" in result.lower()


class TestAddToGroup:
    """Test cases for the node_add_to_group tool."""

    def test_node_add_to_group_basic(self, temp_godot_project: Path, temp_scene_file: Path) -> None:
        """Test adding a node to a group."""
        result = node_add_to_group(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root",
            group_name="enemies",
        )

        assert "added" in result.lower() or "successfully" in result.lower()
        assert "enemies" in result

    def test_node_add_to_group_invalid_node(
        self, temp_godot_project: Path, temp_scene_file: Path
    ) -> None:
        """Test error handling for non-existent node."""
        result = node_add_to_group(
            project_path=str(temp_godot_project),
            scene_path=str(temp_scene_file),
            node_path="root/NonExistent",
            group_name="enemies",
        )

        assert "failed" in result.lower() or "not found" in result.lower()


class TestIntegration:
    """Integration tests combining multiple scene manipulation operations."""

    def test_create_modify_delete_workflow(self, temp_godot_project: Path) -> None:
        """Test a complete workflow of creating, modifying, and deleting nodes."""
        # Create scene
        scene_create(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            root_node_type="Node2D",
        )

        # Add nodes
        node_add(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            node_type="Sprite2D",
            node_name="Player",
        )

        node_add(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            node_type="Sprite2D",
            node_name="Enemy",
        )

        # Rename
        node_rename(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            node_path="root/Player",
            new_name="Hero",
        )

        # Duplicate
        node_duplicate(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            node_path="root/Enemy",
            new_name="Enemy2",
        )

        # Delete original enemy
        node_delete(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
            node_path="root/Enemy",
        )

        # Verify final state
        tree = scene_get_tree(
            project_path=str(temp_godot_project),
            scene_path="workflow.tscn",
        )
        data = json.loads(tree)
        child_names = [c["name"] for c in data.get("children", [])]

        assert "Hero" in child_names
        assert "Enemy2" in child_names
        assert "Player" not in child_names
        assert "Enemy" not in child_names
