#!/usr/bin/env python3
"""Golem MCP — Bridge between Claude Code and Godot 4.x via FastMCP."""

import base64
import logging
from mcp.server.fastmcp import FastMCP, Image
from golem_client import GolemClient

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("golem_mcp")

mcp = FastMCP(
    "godot_mcp",
    instructions=(
        "Godot 4.x editor bridge. Use these tools to inspect, screenshot, "
        "and manipulate scenes in a running Godot editor instance."
    ),
)
client = GolemClient()


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------

async def _ensure_connected():
    await client.ensure_connected()


async def _call(tool: str, args: dict | None = None) -> str | Image:
    """Call Godot and return the appropriate MCP content."""
    from golem_license import is_licensed, is_allowed, increment_trial_usage, get_trial_status

    # Check license/trial status before each call
    allowed, mode = is_allowed()
    if not allowed:
        return "Error: Trial expired. Please activate a license: golem-mcp activate <key>"

    # Increment trial usage if not licensed
    if mode == "trial":
        status = increment_trial_usage()
        remaining = status["remaining"]
        if remaining <= 3 and remaining > 0:
            logger.warning(f"Trial: {remaining} calls remaining")

    await _ensure_connected()
    result = await client.call(tool, args or {})

    if result.get("type") == "image":
        image_data = base64.b64decode(result["result"])
        return Image(data=image_data, format=result.get("mime", "image/png"))

    return result.get("result", "")


# ==================================================================
# Phase 1 — Core tools
# ==================================================================

@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_scene_tree() -> str:
    """Get the current scene tree hierarchy showing node names, types, and attached scripts."""
    return await _call("get_scene_tree")


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_inspect_node(path: str) -> str:
    """Inspect a node's properties, signals, and groups.

    Args:
        path: Node path relative to scene root (e.g. "Player" or "UI/HealthBar")
    """
    return await _call("inspect_node", {"path": path})


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_editor_screenshot() -> Image:
    """Capture a screenshot of the Godot editor viewport."""
    return await _call("get_editor_screenshot")


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_game_screenshot() -> Image:
    """Capture a screenshot of the currently running game."""
    return await _call("get_game_screenshot")


@mcp.tool()
async def godot_play_scene(path: str = "") -> str:
    """Start playing a scene in Godot.

    Args:
        path: Scene file path (e.g. "res://scenes/main.tscn"). Empty = current scene.
    """
    return await _call("play_scene", {"path": path})


@mcp.tool()
async def godot_stop_scene() -> str:
    """Stop the currently running scene."""
    return await _call("stop_scene")


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_errors() -> str:
    """Get recorded errors from the Godot editor."""
    return await _call("get_errors")


@mcp.tool()
async def godot_clear_logs() -> str:
    """Clear the error and log buffers."""
    return await _call("clear_logs")


@mcp.tool()
async def godot_execute_script(code: str) -> str:
    """Execute GDScript code in the editor context and return the result.

    Args:
        code: GDScript code to execute. Will be wrapped in a function body.
              Use 'return <value>' to get a result back.
    """
    return await _call("execute_script", {"code": code})


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_view_script(path: str) -> str:
    """Read the content of a GDScript file.

    Args:
        path: Script file path (e.g. "res://scripts/player.gd")
    """
    return await _call("view_script", {"path": path})


@mcp.tool()
async def godot_write_script(path: str, content: str) -> str:
    """Write a GDScript file to the project. Creates directories if needed.

    Args:
        path: File path (e.g. "res://scripts/player.gd"). Auto-prefixed with res:// if missing.
        content: Full GDScript source code to write.
    """
    return await _call("write_script", {"path": path, "content": content})


@mcp.tool()
async def godot_attach_script(node: str, script: str) -> str:
    """Attach an existing script to a node in the scene.

    Args:
        node: Node path relative to scene root (e.g. "Player"). Use "." for root.
        script: Script file path (e.g. "res://scripts/player.gd").
    """
    return await _call("attach_script", {"node": node, "script": script})


@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_logs(max_lines: int = 100, source: str = "game") -> str:
    """Get logs from the running game or the Godot engine.

    Game logs require using GolemCapture.log("msg") instead of print() in your scripts.
    This ensures output is captured and accessible from Claude Code.

    Args:
        max_lines: Maximum number of log lines to return. Default 100.
        source: "game" for game print output, "engine" for Godot engine logs,
                "all" for both. Default "game".
    """
    return await _call("get_logs", {"max_lines": max_lines, "source": source})


# ==================================================================
# Phase 2 — Scene manipulation
# ==================================================================

@mcp.tool()
async def godot_add_node(
    type: str = "Node",
    name: str = "",
    parent: str = "",
) -> str:
    """Add a new node to the scene tree.

    Args:
        type: Godot node class name (e.g. "Sprite2D", "CharacterBody2D")
        name: Name for the new node. Auto-generated if empty.
        parent: Path to parent node. Empty = scene root.
    """
    return await _call("add_node", {"type": type, "name": name, "parent": parent})


@mcp.tool()
async def godot_delete_node(path: str) -> str:
    """Delete a node from the scene tree.

    Args:
        path: Node path relative to scene root.
    """
    return await _call("delete_node", {"path": path})


@mcp.tool()
async def godot_update_property(path: str, property: str, value) -> str:
    """Update a property on a node.

    Args:
        path: Node path relative to scene root.
        property: Property name (e.g. "position", "modulate", "text")
        value: New value. Use arrays for Vector2/Vector3 (e.g. [100, 200]),
               strings for Color (e.g. "#ff0000"), etc.
    """
    return await _call("update_property", {"path": path, "property": property, "value": value})


@mcp.tool()
async def godot_move_node(path: str, new_parent: str) -> str:
    """Move a node to a different parent in the scene tree.

    Args:
        path: Current node path.
        new_parent: Path to the new parent node.
    """
    return await _call("move_node", {"path": path, "new_parent": new_parent})


@mcp.tool()
async def godot_duplicate_node(path: str) -> str:
    """Duplicate a node and all its children.

    Args:
        path: Node path to duplicate.
    """
    return await _call("duplicate_node", {"path": path})


@mcp.tool()
async def godot_add_nodes(nodes: list) -> str:
    """Create an entire node hierarchy in one call (batch).

    Args:
        nodes: Array of node specs. Each spec is a dict with:
            - name: Node name
            - type: Godot class (e.g. "Sprite2D"). Default "Node"
            - parent: Parent path (optional, default = scene root)
            - properties: Dict of property values to set (optional)
            - children: Array of child node specs (optional, recursive)

    Example:
        [{"name": "Player", "type": "CharacterBody2D", "children": [
            {"name": "Sprite", "type": "Sprite2D"},
            {"name": "Collision", "type": "CollisionShape2D"}
        ]}]
    """
    return await _call("add_nodes", {"nodes": nodes})


@mcp.tool()
async def godot_create_scene(
    path: str,
    root_type: str = "Node2D",
    root_name: str = "",
) -> str:
    """Create a new scene file and open it in the editor.

    Args:
        path: Scene file path (e.g. "res://scenes/main.tscn"). Auto-adds res:// and .tscn if missing.
        root_type: Root node class (e.g. "Node2D", "Node3D", "Control"). Default "Node2D".
        root_name: Name for root node. Default = filename.
    """
    return await _call("create_scene", {"path": path, "root_type": root_type, "root_name": root_name})


@mcp.tool()
async def godot_open_scene(path: str) -> str:
    """Open an existing scene in the editor.

    Args:
        path: Scene file path (e.g. "res://scenes/main.tscn").
    """
    return await _call("open_scene", {"path": path})


# ==================================================================
# Phase 3 — Advanced
# ==================================================================

@mcp.tool(annotations={"readOnlyHint": True})
async def godot_get_input_map() -> str:
    """Get all custom input actions and their bindings."""
    return await _call("get_input_map")


@mcp.tool()
async def godot_project_settings(
    action: str = "list",
    key: str = "",
    value=None,
) -> str:
    """Read or modify Godot project settings.

    Args:
        action: "list" to show common settings, "get" to read one, "set" to modify one.
        key: Setting key (e.g. "application/config/name"). Required for get/set.
        value: New value (only for action="set").
    """
    args = {"action": action, "key": key}
    if value is not None:
        args["value"] = value
    return await _call("project_settings", args)


@mcp.tool()
async def godot_save_scene() -> str:
    """Save the currently edited scene."""
    return await _call("save_scene")


# ------------------------------------------------------------------
# Entry point
# ------------------------------------------------------------------

# ANSI color codes
class Colors:
    CYAN = "\033[96m"      # Bright cyan (vert d'eau)
    TEAL = "\033[38;5;80m" # True teal (256 color)
    DIM = "\033[2m"
    RESET = "\033[0m"
    BOLD = "\033[1m"


GOLEM_BANNER_RAW = """\
   █████████     ███████    █████       ██████████ ██████   ██████
  ███░░░░░███  ███░░░░░███ ░░███       ░░███░░░░░█░░██████ ██████
 ███     ░░░  ███     ░░███ ░███        ░███  █ ░  ░███░█████░███
░███         ░███      ░███ ░███        ░██████    ░███░░███ ░███
░███    █████░███      ░███ ░███        ░███░░█    ░███ ░░░  ░███
░░███  ░░███ ░░███     ███  ░███      █ ░███ ░   █ ░███      ░███
 ░░█████████  ░░░███████░   ███████████ ██████████ █████     █████
  ░░░░░░░░░     ░░░░░░░    ░░░░░░░░░░░ ░░░░░░░░░░ ░░░░░     ░░░░░\
"""


def _supports_color() -> bool:
    """Check if terminal supports colors."""
    import sys
    import os
    # Disable colors if NO_COLOR env is set or not a TTY
    if os.environ.get("NO_COLOR"):
        return False
    if not hasattr(sys.stdout, "isatty"):
        return False
    return sys.stdout.isatty()


def _detect_installed_agents() -> str:
    """Detect which AI assistants are installed and return a label."""
    import shutil
    import sys
    from pathlib import Path

    agents = []

    if shutil.which("claude"):
        agents.append("Claude Code")

    # Cursor
    if sys.platform == "darwin" and Path("/Applications/Cursor.app").exists():
        agents.append("Cursor")
    elif shutil.which("cursor"):
        agents.append("Cursor")

    # Windsurf
    if (Path.home() / ".codeium" / "windsurf").exists():
        agents.append("Windsurf")

    if not agents:
        return "AI Assistants"
    elif len(agents) == 1:
        return agents[0]
    elif len(agents) == 2:
        return f"{agents[0]} & {agents[1]}"
    else:
        return f"{agents[0]} + {len(agents) - 1} more"


def _print_banner(agent: str | None = None):
    """Print the Golem ASCII banner with optional agent tagline."""
    import sys
    import io

    # Fix Windows encoding issues
    if sys.platform == "win32" and hasattr(sys.stdout, "buffer"):
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")

    use_color = _supports_color()

    if use_color:
        c = Colors.TEAL
        r = Colors.RESET
        d = Colors.DIM
    else:
        c = r = d = ""

    print(f"{c}{GOLEM_BANNER_RAW}{r}")

    # Auto-detect agents if not provided
    if agent is None:
        agent = _detect_installed_agents()

    # Dynamic tagline centered under the banner
    tagline = f"Godot 4.x <-> {agent}"
    padding = (67 - len(tagline)) // 2  # 67 is roughly the banner width
    print(f"{d}{' ' * padding}{tagline}{r}")
    print()


def _print_unlicensed_message():
    """Print message when no valid license is found."""
    _print_banner()
    print("  Golem MCP requires a valid license to run.")
    print()
    print("  → Purchase: https://gumroad.com/l/golem-mcp")
    print("  → Activate: golem-mcp activate <YOUR-LICENSE-KEY>")
    print()
    print("  Trial expired? You had 10 free MCP calls.")
    print()


def _run_activate(argv: list[str]) -> None:
    """Handle the activate command."""
    from golem_license import activate, is_licensed

    if not argv:
        print("Usage: golem-mcp activate <license-key>")
        print()
        print("Example: golem-mcp activate GOLEM1-GUM-12345-A3F2B1C9")
        return

    key = argv[0]
    success, message = activate(key)

    if success:
        print(f"✓ {message}")
        print()
        print("You can now use Golem MCP. Next steps:")
        print("  1. golem-mcp install <your-godot-project>")
        print("  2. Enable plugin in Godot > Project Settings > Plugins")
    else:
        print(f"✗ {message}")
        print()
        print("Please check your license key and try again.")
        print("If you need a license: https://gumroad.com/l/golem-mcp")


def _run_status() -> None:
    """Show license status."""
    from golem_license import is_licensed, load_license, get_license_path, get_trial_status

    _print_banner()
    print("  License Status")
    print("  " + "─" * 50)

    key = load_license()
    if key:
        # Mask the key for display
        if len(key) > 20:
            masked = key[:12] + "..." + key[-8:]
        else:
            masked = key
        print(f"    Key file:   {get_license_path()}")
        print(f"    Key:        {masked}")
        print(f"    Status:     {'✓ Valid' if is_licensed() else '✗ Invalid'}")
    else:
        trial = get_trial_status()
        print(f"    Key file:   {get_license_path()}")
        print("    License:    Not installed")
        print(f"    Trial:      {trial['remaining']}/{trial['limit']} calls remaining")
        if trial['remaining'] == 0:
            print("    Status:     ✗ Trial expired")
        else:
            print("    Status:     Trial mode")
        print()
        print("    → Purchase: https://gumroad.com/l/golem-mcp")
    print()


def _run_admin(argv: list[str]) -> None:
    """Handle admin commands (requires GOLEM_ADMIN env var)."""
    import os

    if not os.environ.get("GOLEM_ADMIN"):
        print("✗ Admin commands require GOLEM_ADMIN environment variable")
        return

    if not argv:
        print("Admin commands:")
        print("  golem-mcp admin keygen <order_id>   Generate a license key")
        print("  golem-mcp admin verify <key>        Verify a license key")
        print("  golem-mcp admin bulk <file>         Generate keys from order IDs file")
        return

    subcmd = argv[0]

    if subcmd == "keygen":
        if len(argv) < 2:
            print("Usage: golem-mcp admin keygen <order_id>")
            print("Example: golem-mcp admin keygen GUM-abc123")
            return
        from golem_license import generate_key
        order_id = argv[1]
        key = generate_key(order_id)
        print(f"Order ID: {order_id}")
        print(f"Key:      {key}")

    elif subcmd == "verify":
        if len(argv) < 2:
            print("Usage: golem-mcp admin verify <key>")
            return
        from golem_license import verify_key
        key = argv[1]
        valid = verify_key(key)
        print(f"Key:    {key}")
        print(f"Valid:  {'✓ Yes' if valid else '✗ No'}")

    elif subcmd == "bulk":
        if len(argv) < 2:
            print("Usage: golem-mcp admin bulk <file>")
            print("File format: one order ID per line")
            return
        from golem_license import generate_key
        from pathlib import Path
        filepath = Path(argv[1])
        if not filepath.exists():
            print(f"✗ File not found: {filepath}")
            return
        order_ids = filepath.read_text().strip().splitlines()
        print(f"Generating {len(order_ids)} keys...")
        print()
        for order_id in order_ids:
            order_id = order_id.strip()
            if order_id:
                key = generate_key(order_id)
                print(f"{order_id},{key}")

    elif subcmd == "reset-trial":
        from golem_license import reset_trial, get_trial_status
        reset_trial()
        status = get_trial_status()
        print(f"✓ Trial reset: {status['remaining']}/{status['limit']} calls available")

    else:
        print(f"✗ Unknown admin command: {subcmd}")
        print("  Available: keygen, verify, bulk, reset-trial")


def main():
    import sys
    from golem_license import is_allowed

    # Commands that don't require a license
    if len(sys.argv) > 1:
        cmd = sys.argv[1]

        if cmd == "activate":
            _run_activate(sys.argv[2:])
            return

        if cmd == "status":
            _run_status()
            return

        if cmd == "admin":
            _run_admin(sys.argv[2:])
            return

        if cmd == "install":
            # License or trial check for install
            allowed, mode = is_allowed()
            if not allowed:
                _print_unlicensed_message()
                sys.exit(1)
            from golem_installer import run_install
            run_install(sys.argv[2:])
            return

        if cmd == "uninstall":
            # License or trial check for uninstall
            allowed, mode = is_allowed()
            if not allowed:
                _print_unlicensed_message()
                sys.exit(1)
            from golem_installer import run_uninstall
            run_uninstall(sys.argv[2:])
            return

        if cmd in ("--help", "-h", "help"):
            _print_banner()
            print("  Bridge between Claude Code and Godot 4.x")
            print()
            print("  Usage:")
            print("    golem-mcp                     Run the MCP server")
            print("    golem-mcp install <project>   Install in a Godot project")
            print("    golem-mcp uninstall <project> Remove from a project")
            print("    golem-mcp activate <key>      Activate a license key")
            print("    golem-mcp status              Show license status")
            print()
            print("  Install options:")
            print("    --agent <name>   claude, cursor, windsurf, continue, cline")
            print("    --force          Overwrite existing installation")
            print("    --local <path>   Use local dev server instead of uvx")
            print("    --skip-skills    Don't copy .claude/commands/")
            print()
            print("  Trial: 10 free MCP calls. Use 'golem-mcp status' to check.")
            print()
            return

        if cmd == "--version":
            from golem_installer import _get_version
            print(f"Golem MCP v{_get_version()}")
            return

    # Running the MCP server requires a license or trial
    allowed, mode = is_allowed()
    if not allowed:
        _print_unlicensed_message()
        sys.exit(1)

    if mode == "trial":
        from golem_license import get_trial_status
        status = get_trial_status()
        logger.info(f"Running in trial mode: {status['remaining']}/{status['limit']} calls remaining")

    mcp.run()


if __name__ == "__main__":
    main()
