"""
Local command execution — runs commands on the user's machine at the agent's request.

Security model:
  1. Known EDA commands (verilator, vcs, xrun, etc.) auto-approve
  2. Unknown commands show the command and ask the user to approve
  3. All commands run in the project directory (no escape)
  4. Hard timeout of 5 minutes per command
  5. stdout/stderr capped at 500KB/100KB
"""

from __future__ import annotations

import asyncio
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional

from rich.console import Console
from rich.panel import Panel

console = Console()

# Commands that are auto-approved without user confirmation.
# These are standard EDA tools that the agent is expected to use.
AUTO_APPROVE_PREFIXES = [
    # Open source
    "verilator", "iverilog", "vvp", "yosys", "make", "cmake",
    # Synopsys
    "vcs", "simv", "urg", "vcf", "verdi", "spyglass", "sg_shell",
    # Cadence
    "xrun", "xmsim", "xmvlog", "xmvhdl", "xmelab", "imc", "jg",
    "irun", "ncsim", "ncvlog",
    # Siemens
    "vlog", "vopt", "vsim", "vlib", "vmap", "vcover", "qverify", "visualizer",
    # AMD/Xilinx
    "xvlog", "xvhdl", "xelab", "xsim", "xcrg", "vivado",
    # Common utilities
    "ls", "cat", "head", "tail", "grep", "find", "wc", "diff",
    "mkdir", "cp", "mv", "rm", "touch", "chmod",
    "python3", "python", "pip",
    "git", "which", "echo", "env", "pwd", "cd",
]

COMMAND_TIMEOUT = 300  # 5 minutes
MAX_STDOUT = 500_000   # 500 KB
MAX_STDERR = 100_000   # 100 KB

# Blocked patterns that should never execute
BLOCKED_PATTERNS = [
    "curl ", "wget ", "ssh ", "scp ", "rsync ",
    "nc ", "netcat ", "ncat ",
    "> /dev/", "| /dev/",
    "sudo ", "su ",
    "rm -rf /", "rm -rf ~",
    "mkfs", "dd if=",
    ":(){ ", "fork",
]


def _is_auto_approved(command: str) -> bool:
    """Check if a command matches the auto-approve allowlist."""
    cmd_stripped = command.strip()
    first_word = cmd_stripped.split()[0] if cmd_stripped else ""
    base_cmd = os.path.basename(first_word)
    return any(base_cmd.startswith(prefix) or first_word.startswith(f"./{prefix}")
               for prefix in AUTO_APPROVE_PREFIXES)


def _is_blocked(command: str) -> bool:
    """Check if a command matches blocked patterns."""
    cmd_lower = command.lower()
    return any(pattern in cmd_lower for pattern in BLOCKED_PATTERNS)


async def execute_local_command(
    command: str,
    cwd: str = ".",
    auto_approve: bool = False,
) -> dict:
    """Execute a command locally with security controls.

    Args:
        command: Shell command to execute.
        cwd: Working directory (must be within the project).
        auto_approve: Skip user approval for all commands (dangerous).

    Returns:
        {"stdout": str, "stderr": str, "exit_code": int}
    """
    # Security: block dangerous commands
    if _is_blocked(command):
        return {
            "stdout": "",
            "stderr": f"BLOCKED: Command rejected by security policy: {command[:100]}",
            "exit_code": -1,
        }

    # Security: check auto-approve or prompt user
    approved = auto_approve or _is_auto_approved(command)

    if not approved:
        console.print()
        console.print(Panel(
            f"[bold yellow]The agent wants to run a command on your machine:[/bold yellow]\n\n"
            f"  [bold white]{command}[/bold white]\n\n"
            f"[dim]Working directory: {cwd}[/dim]",
            title="Remote Command Request",
            border_style="yellow",
        ))
        try:
            response = input("  Allow? (y/n/always): ").strip().lower()
        except (EOFError, KeyboardInterrupt):
            return {
                "stdout": "",
                "stderr": "Command rejected by user",
                "exit_code": -1,
            }

        if response in ("always", "a"):
            auto_approve = True
            approved = True
        elif response not in ("y", "yes"):
            console.print("  [red]Rejected.[/red]")
            return {
                "stdout": "",
                "stderr": "Command rejected by user",
                "exit_code": -1,
            }

    # Show what's running
    cmd_short = command[:120] + ("..." if len(command) > 120 else "")
    console.print(f"  [dim]Running locally:[/dim] [cyan]{cmd_short}[/cyan]")

    # Execute with timeout
    try:
        proc = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            cwd=cwd,
        )
        try:
            stdout_bytes, stderr_bytes = await asyncio.wait_for(
                proc.communicate(), timeout=COMMAND_TIMEOUT
            )
        except asyncio.TimeoutError:
            proc.kill()
            await proc.communicate()
            console.print(f"  [red]Timed out after {COMMAND_TIMEOUT}s[/red]")
            return {
                "stdout": "",
                "stderr": f"Command timed out after {COMMAND_TIMEOUT} seconds",
                "exit_code": -1,
            }

        stdout = stdout_bytes.decode("utf-8", errors="replace")[:MAX_STDOUT]
        stderr = stderr_bytes.decode("utf-8", errors="replace")[:MAX_STDERR]
        exit_code = proc.returncode or 0

        # Brief status
        if exit_code == 0:
            console.print(f"  [green]Done[/green] [dim](exit 0, {len(stdout)} chars)[/dim]")
        else:
            console.print(f"  [red]Failed[/red] [dim](exit {exit_code})[/dim]")

        return {"stdout": stdout, "stderr": stderr, "exit_code": exit_code}

    except Exception as e:
        console.print(f"  [red]Error: {e}[/red]")
        return {
            "stdout": "",
            "stderr": f"Execution error: {e}",
            "exit_code": -1,
        }
