"""Tool definitions and synchronous executors for Agent Johnny 5.

v1.1: Conversation-only tools. The agent does NOT drive the install.
Tools: run_shell_command, read_file, write_file.

These are used for post-install help, troubleshooting, and channel
setup. All executors run inside the agent's background thread — they
use subprocess.run() and open() (blocking), NOT async equivalents.
"""

from __future__ import annotations

import json
import logging
import os
import subprocess
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from textual.app import App
    from ..state import InstallState

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# OpenAI-compatible tool definitions (sent to the LLM)
# ---------------------------------------------------------------------------

TOOL_DEFINITIONS: list[dict] = [
    {
        "type": "function",
        "function": {
            "name": "run_shell_command",
            "description": (
                "Execute a shell command on the server. "
                "Returns stdout, stderr, and return code. "
                "Use for installing packages, running CLI commands, "
                "checking services, diagnostics, etc."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {
                        "type": "string",
                        "description": "The shell command to execute",
                    },
                    "timeout": {
                        "type": "integer",
                        "description": "Timeout in seconds (default 120)",
                    },
                },
                "required": ["command"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "Read the full contents of a file on the server.",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "Absolute file path to read",
                    },
                },
                "required": ["path"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "write_file",
            "description": (
                "Write content to a file on the server. "
                "Creates parent directories if needed. "
                "Overwrites existing content."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "Absolute file path to write",
                    },
                    "content": {
                        "type": "string",
                        "description": "Content to write to the file",
                    },
                },
                "required": ["path", "content"],
            },
        },
    },
]


# ---------------------------------------------------------------------------
# Synchronous tool executors (run in agent's background thread)
# ---------------------------------------------------------------------------

_BLOCKED_COMMANDS = frozenset([
    "rm -rf /",
    "rm -rf /*",
    "mkfs",
    "dd if=/dev/zero",
    ":(){:|:&};:",
])


def execute_tool(
    name: str, args: dict, state: "InstallState", app: "App"
) -> str:
    """Execute a tool by name and return the result as a JSON string."""
    if name == "run_shell_command":
        return _run_shell_command(args)
    elif name == "read_file":
        return _read_file(args)
    elif name == "write_file":
        return _write_file(args)
    else:
        return json.dumps({"error": f"Unknown tool: {name}"})


def _run_shell_command(args: dict) -> str:
    """Execute a shell command via subprocess.run (blocking)."""
    command = args.get("command", "")
    timeout = args.get("timeout", 120)

    cmd_lower = command.strip().lower()
    for blocked in _BLOCKED_COMMANDS:
        if blocked in cmd_lower:
            return json.dumps({
                "error": f"Blocked: '{command}' matches safety blocklist"
            })

    try:
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            timeout=timeout,
            text=True,
        )
        return json.dumps({
            "stdout": result.stdout[:8000],
            "stderr": result.stderr[:4000],
            "returncode": result.returncode,
        })
    except subprocess.TimeoutExpired:
        return json.dumps({"error": f"Command timed out after {timeout}s"})
    except Exception as exc:
        return json.dumps({"error": str(exc)})


def _read_file(args: dict) -> str:
    """Read file contents (blocking I/O)."""
    path = args.get("path", "")
    try:
        with open(path, "r", errors="replace") as f:
            content = f.read(100_000)
        return json.dumps({"content": content})
    except Exception as exc:
        return json.dumps({"error": str(exc)})


def _write_file(args: dict) -> str:
    """Write content to a file (blocking I/O)."""
    path = args.get("path", "")
    content = args.get("content", "")
    try:
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, "w") as f:
            f.write(content)
        return json.dumps({"success": True, "path": path})
    except Exception as exc:
        return json.dumps({"error": str(exc)})
