"""
Bash Execution Tool

Tool for executing shell commands with safety controls.
"""

import os
import subprocess
import shlex
import threading
from pathlib import Path
from typing import Optional, Set

from .base import Tool, ToolResult, ToolParameter, ToolPermission


# Dangerous commands that should always be blocked or warned about
DANGEROUS_COMMANDS = {
    # Destructive commands
    "rm -rf /", "rm -rf /*", "rm -rf ~", "rm -rf ~/*",
    "mkfs", "dd if=/dev/zero", "dd if=/dev/random",
    "> /dev/sda", "chmod -R 777 /", "chown -R",

    # System modification
    "shutdown", "reboot", "init 0", "init 6",
    "halt", "poweroff",

    # Dangerous patterns
    ":(){:|:&};:",  # Fork bomb
}

# Commands that need extra caution (will still ask for confirmation)
CAUTIOUS_COMMANDS = {
    "rm", "rmdir", "mv", "dd", "chmod", "chown",
    "kill", "killall", "pkill",
    "sudo", "su",
    "curl | sh", "wget | sh", "curl | bash", "wget | bash",
}


class BashTool(Tool):
    """Execute bash commands"""

    name = "Bash"
    description = (
        "Execute a bash command in the shell. "
        "Use for running scripts, git commands, package managers, etc. "
        "Commands run in the current working directory."
    )
    category = "execution"
    permission = ToolPermission.ASK  # Always ask before executing

    parameters = [
        ToolParameter(
            name="command",
            description="The bash command to execute",
            type="string",
            required=True,
        ),
        ToolParameter(
            name="timeout",
            description="Maximum execution time in seconds (default: 120)",
            type="integer",
            required=False,
            default=120,
        ),
        ToolParameter(
            name="cwd",
            description="Working directory for the command (default: current directory)",
            type="string",
            required=False,
        ),
    ]

    def __init__(self):
        super().__init__()
        self._running_processes: Set[subprocess.Popen] = set()

    def execute(
        self,
        command: str,
        timeout: int = 120,
        cwd: str = None,
    ) -> ToolResult:
        """Execute a bash command"""

        # Safety checks
        safety_result = self._check_safety(command)
        if safety_result:
            return safety_result

        # Resolve working directory
        if cwd:
            work_dir = Path(cwd).expanduser()
            if not work_dir.exists():
                return ToolResult(
                    success=False,
                    output="",
                    error=f"Working directory not found: {cwd}",
                    target=command[:40],
                )
        else:
            work_dir = Path.cwd()

        try:
            # Execute command
            process = subprocess.Popen(
                command,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                cwd=str(work_dir),
                text=True,
                env={**os.environ, "TERM": "dumb"},  # Avoid color codes
            )

            self._running_processes.add(process)

            try:
                stdout, stderr = process.communicate(timeout=timeout)
            except subprocess.TimeoutExpired:
                process.kill()
                stdout, stderr = process.communicate()
                return ToolResult(
                    success=False,
                    output=stdout or "",
                    error=f"Command timed out after {timeout} seconds\n{stderr}",
                    target=command[:40],
                )
            finally:
                self._running_processes.discard(process)

            # Truncate output if too large
            max_output = 50000
            if len(stdout) > max_output:
                stdout = stdout[:max_output] + f"\n... (output truncated, {len(stdout)} total chars)"

            if len(stderr) > max_output:
                stderr = stderr[:max_output] + f"\n... (stderr truncated)"

            # Format output
            if process.returncode == 0:
                output = stdout
                if stderr:
                    output += f"\n\nStderr:\n{stderr}"
                return ToolResult(
                    success=True,
                    output=output or "(no output)",
                    target=command[:40],
                    data={
                        "return_code": process.returncode,
                        "cwd": str(work_dir),
                    },
                )
            else:
                return ToolResult(
                    success=False,
                    output=stdout,
                    error=f"Command failed with exit code {process.returncode}\n{stderr}",
                    target=command[:40],
                    data={"return_code": process.returncode},
                )

        except Exception as e:
            return ToolResult(
                success=False,
                output="",
                error=f"Error executing command: {e}",
                target=command[:40],
            )

    def _check_safety(self, command: str) -> Optional[ToolResult]:
        """Check if command is safe to execute"""
        cmd_lower = command.lower().strip()

        # Check for dangerous commands
        for dangerous in DANGEROUS_COMMANDS:
            if dangerous in cmd_lower:
                return ToolResult(
                    success=False,
                    output="",
                    error=f"Dangerous command blocked: {command[:50]}",
                    target=command[:40],
                )

        # Check for common dangerous patterns
        if cmd_lower.startswith("rm ") and " -rf" in cmd_lower:
            # Check if it's trying to delete important paths
            dangerous_paths = ["/", "/*", "~", "~/*", "/home", "/usr", "/etc", "/var"]
            for path in dangerous_paths:
                if path in cmd_lower:
                    return ToolResult(
                        success=False,
                        output="",
                        error=f"Refusing to delete system path: {path}",
                        target=command[:40],
                    )

        return None

    def kill_all(self) -> None:
        """Kill all running processes"""
        for process in list(self._running_processes):
            try:
                process.kill()
            except Exception:
                pass
        self._running_processes.clear()


class BackgroundBashTool(Tool):
    """Execute bash commands in the background"""

    name = "BackgroundBash"
    description = (
        "Execute a bash command in the background. "
        "Returns immediately while command runs asynchronously. "
        "Use for long-running processes like servers."
    )
    category = "execution"
    permission = ToolPermission.ASK

    parameters = [
        ToolParameter(
            name="command",
            description="The bash command to execute in background",
            type="string",
            required=True,
        ),
        ToolParameter(
            name="cwd",
            description="Working directory for the command",
            type="string",
            required=False,
        ),
    ]

    _background_processes: dict = {}  # class-level storage
    _next_id: int = 1

    def execute(self, command: str, cwd: str = None) -> ToolResult:
        """Execute command in background"""

        work_dir = Path(cwd).expanduser() if cwd else Path.cwd()

        if not work_dir.exists():
            return ToolResult(
                success=False,
                output="",
                error=f"Working directory not found: {cwd}",
                target=command[:40],
            )

        try:
            process = subprocess.Popen(
                command,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                cwd=str(work_dir),
                text=True,
            )

            # Store process
            process_id = f"bg_{BackgroundBashTool._next_id}"
            BackgroundBashTool._next_id += 1
            BackgroundBashTool._background_processes[process_id] = {
                "process": process,
                "command": command,
                "cwd": str(work_dir),
            }

            return ToolResult(
                success=True,
                output=f"Started background process: {process_id}\nPID: {process.pid}\nCommand: {command[:50]}",
                target=command[:40],
                data={
                    "process_id": process_id,
                    "pid": process.pid,
                },
            )

        except Exception as e:
            return ToolResult(
                success=False,
                output="",
                error=f"Error starting background process: {e}",
                target=command[:40],
            )

    @classmethod
    def get_process(cls, process_id: str):
        """Get a background process by ID"""
        return cls._background_processes.get(process_id)

    @classmethod
    def kill_process(cls, process_id: str) -> bool:
        """Kill a background process"""
        info = cls._background_processes.get(process_id)
        if info:
            try:
                info["process"].kill()
                del cls._background_processes[process_id]
                return True
            except Exception:
                return False
        return False


def register_bash_tools(registry):
    """Register bash tools with a registry"""
    registry.register_class(BashTool)
    registry.register_class(BackgroundBashTool)
