"""Node Domain Tools for Godot MCP.

This module contains all MCP tools related to node operations in Godot scenes,
including adding, deleting, renaming, reparenting, duplicating nodes, and
managing node properties and groups.
"""

import asyncio
from pathlib import Path

from godot_mcp.core import (
    create_error_response,
    extract_json_from_output,
    godot_server,
    mcp,
    validate_path,
)

__all__ = [
    "node_add",
    "node_add_to_group",
    "node_delete",
    "node_duplicate",
    "node_get_properties",
    "node_load_sprite_texture",
    "node_rename",
    "node_reparent",
    "node_set_property",
]


@mcp.tool()
def node_add(
    project_path: str,
    scene_path: str,
    node_type: str,
    node_name: str,
    parent_node_path: str = None,
    properties: dict = None,
) -> str:
    """Add a node to an existing scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_type: Type of node to add (e.g., Sprite2D, CollisionShape2D)
        node_name: Name for the new node
        parent_node_path: Path to the parent node (e.g., "root" or "root/Player")
        properties: Optional properties to set on the node
    """
    if not project_path or not scene_path or not node_type or not node_name:
        return create_error_response("Missing required parameters")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        # Check if the project directory exists and contains a project.godot file
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        # Check if the scene file exists
        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                [
                    "Ensure the scene path is correct",
                    "Use scene_create to create a new scene first",
                ],
            )

        # Prepare parameters for the operation
        params = {
            "scenePath": scene_path,
            "nodeType": node_type,
            "nodeName": node_name,
        }

        # Add optional parameters
        if parent_node_path:
            params["parentNodePath"] = parent_node_path
        if properties:
            params["properties"] = properties

        # Execute the operation
        stdout, stderr = asyncio.run(
            godot_server.execute_operation("add_node", params, project_path)
        )

        if stderr and "Failed to" in stderr:
            return create_error_response(
                f"Failed to add node: {stderr}",
                [
                    "Check if the node type is valid",
                    "Ensure the parent node path is correct",
                ],
            )

        return f"Node '{node_name}' of type '{node_type}' added successfully to the scene."
    except Exception as error:
        return create_error_response(
            f"Failed to add node: {error}",
            [
                "Ensure Godot is installed correctly",
                "Verify the project path is accessible",
            ],
        )


@mcp.tool()
def node_delete(
    project_path: str,
    scene_path: str,
    node_path: str,
) -> str:
    """Delete a node from a scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node to delete (e.g., "root/Player")

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path:
        return create_error_response("Missing required parameters")

    if node_path == "root" or node_path == "":
        return create_error_response("Cannot delete the root node")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
        }

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("delete_node", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to delete node: {stderr}",
                ["Check if the node path is correct"],
            )

        return f"Node '{node_path}' deleted successfully from scene '{scene_path}'."
    except Exception as error:
        return create_error_response(
            f"Failed to delete node: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_rename(
    project_path: str,
    scene_path: str,
    node_path: str,
    new_name: str,
) -> str:
    """Rename a node in a scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node to rename (e.g., "root/OldName")
        new_name: New name for the node

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path or not new_name:
        return create_error_response("Missing required parameters")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "newName": new_name,
        }

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("rename_node", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to rename node: {stderr}",
                ["Check if the node path is correct"],
            )

        return f"Node renamed from '{node_path.split('/')[-1]}' to '{new_name}'."
    except Exception as error:
        return create_error_response(
            f"Failed to rename node: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_reparent(
    project_path: str,
    scene_path: str,
    node_path: str,
    new_parent_path: str,
) -> str:
    """Move a node to a new parent in a scene (reparent).

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node to move (e.g., "root/Player")
        new_parent_path: Path to the new parent node (e.g., "root/Entities")

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path or not new_parent_path:
        return create_error_response("Missing required parameters")

    if node_path == "root":
        return create_error_response("Cannot move the root node")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "newParentPath": new_parent_path,
        }

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("move_node", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to move node: {stderr}",
                ["Check if the node paths are correct"],
            )

        return f"Node '{node_path}' moved to parent '{new_parent_path}'."
    except Exception as error:
        return create_error_response(
            f"Failed to move node: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_duplicate(
    project_path: str,
    scene_path: str,
    node_path: str,
    new_name: str = None,
) -> str:
    """Duplicate a node in a scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node to duplicate (e.g., "root/Player")
        new_name: Optional name for the duplicate (default: original name + number)

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path:
        return create_error_response("Missing required parameters")

    if node_path == "root":
        return create_error_response("Cannot duplicate the root node")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
        }

        if new_name:
            params["newName"] = new_name

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("duplicate_node", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to duplicate node: {stderr}",
                ["Check if the node path is correct"],
            )

        dup_name = new_name if new_name else f"{node_path.split('/')[-1]}2"
        return f"Node '{node_path}' duplicated as '{dup_name}'."
    except Exception as error:
        return create_error_response(
            f"Failed to duplicate node: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_set_property(
    project_path: str,
    scene_path: str,
    node_path: str,
    property_name: str,
    property_value: int | float | str | bool | dict | list,
) -> str:
    """Set a property value on a node in a scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node (e.g., "root" or "root/Player")
        property_name: Name of the property to set (e.g., "position", "scale", "visible")
        property_value: Value to set (supports int, float, string, bool, dict for Vector2/3, list)

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path or not property_name:
        return create_error_response("Missing required parameters")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "propertyName": property_name,
            "propertyValue": property_value,
        }

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("set_node_property", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to set node property: {stderr}",
                ["Check if the node path and property name are correct"],
            )

        return f"Property '{property_name}' set successfully on node '{node_path}'."
    except Exception as error:
        return create_error_response(
            f"Failed to set node property: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_get_properties(
    project_path: str,
    scene_path: str,
    node_path: str,
    include_inherited: bool = False,
) -> str:
    """Get all properties of a specific node in a scene.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node within the scene (e.g., "root" or "root/Player/Sprite2D")
        include_inherited: Whether to include inherited properties (default: False)

    Returns:
        JSON containing the node's properties with their names, values, and types
    """
    if not project_path or not scene_path or not node_path:
        return create_error_response("Project path, scene path, and node path are required")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        # Check if the project directory exists and contains a project.godot file
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        # Check if the scene file exists
        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                [
                    "Ensure the scene path is correct",
                    "Use scene_list to see available scenes",
                ],
            )

        # Prepare parameters for the operation
        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "includeInherited": include_inherited,
        }

        # Execute the operation
        stdout, stderr = asyncio.run(
            godot_server.execute_operation("get_node_properties", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to get node properties: {stderr}",
                [
                    "Check if the node path is correct",
                    "Use scene_get_tree to see available nodes",
                ],
            )

        # Extract and return the JSON output from the GDScript
        return extract_json_from_output(stdout)
    except Exception as error:
        return create_error_response(
            f"Failed to get node properties: {error}",
            [
                "Ensure Godot is installed correctly",
                "Verify the project path is accessible",
            ],
        )


@mcp.tool()
def node_add_to_group(
    project_path: str,
    scene_path: str,
    node_path: str,
    group_name: str,
    persistent: bool = True,
) -> str:
    """Add a node to a group.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the node (e.g., "root/Player")
        group_name: Name of the group to add the node to
        persistent: Whether the group membership persists when saved (default: True)

    Returns:
        Success message or error description
    """
    if not project_path or not scene_path or not node_path or not group_name:
        return create_error_response("Missing required parameters")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        project_file = Path(project_path) / "project.godot"
        if not project_file.exists():
            return create_error_response(
                f"Not a valid Godot project: {project_path}",
                ["Ensure the path points to a directory containing a project.godot file"],
            )

        scene_full_path = Path(project_path) / scene_path
        if not scene_full_path.exists():
            return create_error_response(
                f"Scene file does not exist: {scene_path}",
                ["Ensure the scene path is correct"],
            )

        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "groupName": group_name,
            "persistent": persistent,
        }

        stdout, stderr = asyncio.run(
            godot_server.execute_operation("add_to_group", params, project_path)
        )

        if stderr and ("Failed to" in stderr or "not found" in stderr.lower()):
            return create_error_response(
                f"Failed to add node to group: {stderr}",
                ["Check if the node path is correct"],
            )

        return f"Node '{node_path}' added to group '{group_name}'."
    except Exception as error:
        return create_error_response(
            f"Failed to add node to group: {error}",
            ["Ensure Godot is installed correctly"],
        )


@mcp.tool()
def node_load_sprite_texture(
    project_path: str, scene_path: str, node_path: str, texture_path: str
) -> str:
    """Load a sprite into a Sprite2D node.

    Category: Node

    Args:
        project_path: Path to the Godot project directory
        scene_path: Path to the scene file (relative to project)
        node_path: Path to the Sprite2D node (e.g., "root/Player/Sprite2D")
        texture_path: Path to the texture file (relative to project)
    """
    if not project_path or not scene_path or not node_path or not texture_path:
        return create_error_response("Missing required parameters")

    if not validate_path(project_path) or not validate_path(scene_path):
        return create_error_response("Invalid path")

    try:
        # Prepare parameters for the operation
        params = {
            "scenePath": scene_path,
            "nodePath": node_path,
            "texturePath": texture_path,
        }

        # Execute the operation
        stdout, stderr = asyncio.run(
            godot_server.execute_operation("load_sprite", params, project_path)
        )

        if stderr and "Failed to" in stderr:
            return create_error_response(
                f"Failed to load sprite: {stderr}",
                ["Check if the texture file exists", "Ensure the node path is correct"],
            )

        return f"Sprite loaded successfully with texture: {texture_path}"
    except Exception as error:
        return create_error_response(
            f"Failed to load sprite: {error}",
            [
                "Ensure Godot is installed correctly",
                "Verify the project path is accessible",
            ],
        )
