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

v0.9.0: Step-based tools (restart_step, resume_pipeline) removed.
New terminal interaction tools added: send_to_terminal,
read_terminal_screen, interrupt_terminal.

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 signal
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 in a SEPARATE shell "
                "(not the terminal pane). Returns stdout, stderr, and return code. "
                "Use for diagnostics, checking services, reading configs, etc."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {
                        "type": "string",
                        "description": "The shell command to execute",
                    },
                    "timeout": {
                        "type": "integer",
                        "description": "Timeout in seconds (default 30)",
                    },
                },
                "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"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "send_to_terminal",
            "description": (
                "Type text into the terminal (the installer PTY). "
                "Use this to answer interactive prompts or type commands "
                "into the running installer. The text is sent exactly as given — "
                "append \\n for Enter. Always tell the user what you are about "
                "to type BEFORE calling this tool."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "text": {
                        "type": "string",
                        "description": (
                            "Text to type into the terminal. "
                            "Use \\n for Enter, \\x03 for Ctrl+C."
                        ),
                    },
                },
                "required": ["text"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "read_terminal_screen",
            "description": (
                "Get the current visible terminal screen content. "
                "Returns what's currently displayed in the terminal pane "
                "(not scrollback). Useful for seeing prompts, menus, or "
                "the current state of an interactive program."
            ),
            "parameters": {
                "type": "object",
                "properties": {},
                "required": [],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "interrupt_terminal",
            "description": (
                "Send an interrupt signal to the terminal subprocess. "
                "Use when the installer appears hung or stuck. "
                "Signal 'SIGINT' sends Ctrl+C, 'SIGTERM' terminates, "
                "'SIGKILL' force-kills."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "signal": {
                        "type": "string",
                        "enum": ["SIGINT", "SIGTERM", "SIGKILL"],
                        "description": "Signal to send (default SIGINT)",
                    },
                },
                "required": [],
            },
        },
    },
]


# ---------------------------------------------------------------------------
# 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)
    elif name == "send_to_terminal":
        return _send_to_terminal(args, state)
    elif name == "read_terminal_screen":
        return _read_terminal_screen(state)
    elif name == "interrupt_terminal":
        return _interrupt_terminal(args, state)
    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", 30)

    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)})


def _send_to_terminal(args: dict, state: InstallState) -> str:
    """Type text into the PTY terminal."""
    text = args.get("text", "")
    if not text:
        return json.dumps({"error": "text is required"})

    terminal = state.terminal
    if terminal is None:
        return json.dumps({"error": "Terminal not available"})

    if not terminal.is_alive:
        return json.dumps({"error": "Terminal process has exited"})

    # Process escape sequences in the text
    text = text.replace("\\n", "\n").replace("\\r", "\r").replace("\\x03", "\x03")
    terminal.write_input(text)
    return json.dumps({"success": True, "sent": repr(text)})


def _read_terminal_screen(state: InstallState) -> str:
    """Get the current visible terminal screen."""
    terminal = state.terminal
    if terminal is None:
        return json.dumps({"error": "Terminal not available"})

    screen_text = terminal.get_screen_text()
    return json.dumps({"screen": screen_text})


def _interrupt_terminal(args: dict, state: InstallState) -> str:
    """Send a signal to the terminal subprocess."""
    signal_name = args.get("signal", "SIGINT")
    terminal = state.terminal
    if terminal is None:
        return json.dumps({"error": "Terminal not available"})

    pid = terminal.child_pid
    if pid is None or not terminal.is_alive:
        return json.dumps({"error": "No active terminal process"})

    sig_map = {
        "SIGINT": signal.SIGINT,
        "SIGTERM": signal.SIGTERM,
        "SIGKILL": signal.SIGKILL,
    }
    sig = sig_map.get(signal_name, signal.SIGINT)

    try:
        os.killpg(os.getpgid(pid), sig)
        return json.dumps({
            "success": True,
            "pid": pid,
            "signal": signal_name,
        })
    except ProcessLookupError:
        return json.dumps({"error": "Process already terminated"})
    except Exception as exc:
        return json.dumps({"error": str(exc)})
