"""
Shell Command Execution - Run shell commands inline with !command syntax

Provides inline shell command execution for the CLI:
- Detect `!` prefix in input text
- Execute command immediately
- Show output in terminal
- Option to include output in prompt
- Maintain command history

Security Features (SEC-015):
- Dangerous command blocking
- Secret masking in output and history
- Integration with bash_ops security features

Examples:
    # Run a command directly
    runner = ShellRunner()
    result = runner.run("git status")

    # Check for and run !commands in text
    if runner.is_shell_command("!git status"):
        result = runner.run_from_input("!git status")

    # Get command history
    history = runner.get_history()
"""

import re
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Callable, Optional

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

# SEC-015: Import security functions from bash_ops
from groknroll.operations.bash_ops import mask_secrets


# SEC-015: Dangerous commands to block
DANGEROUS_COMMANDS = {
    "rm -rf /",
    "dd if=",
    "mkfs",
    ":(){ :|:& };:",  # Fork bomb
    "chmod +s",
    "chmod 777",
    "chmod -R 777",
    "sudo rm -rf",
    "curl | bash",
    "curl | sh",
    "wget | bash",
    "wget | sh",
    "curl|bash",
    "curl|sh",
    "eval ",
    "> /dev/sd",
    "mv /* ",
    "rm /* ",
    "kill -9 -1",
    "> /etc/passwd",
    "> /etc/shadow",
    "rm -rf /*",
}


def _is_dangerous_command(command: str) -> bool:
    """
    Check if command is dangerous and should be blocked.

    SEC-015: Shell injection prevention.

    Args:
        command: The command to check

    Returns:
        True if command is dangerous
    """
    command_lower = command.lower()
    for pattern in DANGEROUS_COMMANDS:
        if pattern.lower() in command_lower:
            return True
    return False


@dataclass
class CommandExecution:
    """Record of a command execution"""

    command: str
    stdout: str
    stderr: str
    exit_code: int
    success: bool
    cwd: str
    timestamp: datetime
    timed_out: bool = False

    @property
    def output(self) -> str:
        """Combined stdout and stderr output"""
        parts = []
        if self.stdout:
            parts.append(self.stdout)
        if self.stderr:
            parts.append(self.stderr)
        return "\n".join(parts)


@dataclass
class ShellRunnerResult:
    """Result from running a shell command"""

    execution: Optional[CommandExecution]
    success: bool
    error: Optional[str] = None
    included_in_prompt: bool = False


@dataclass
class CommandHistoryEntry:
    """Entry in command history"""

    command: str
    timestamp: datetime
    success: bool
    exit_code: int


class ShellRunner:
    """
    Execute shell commands from CLI input with ! prefix

    Provides immediate shell command execution with output display
    and history tracking.

    Example:
        runner = ShellRunner()

        # Run a command
        result = runner.run("ls -la")
        print(result.execution.stdout)

        # Check for !command prefix
        if runner.is_shell_command("!git status"):
            result = runner.run_from_input("!git status")
    """

    # Pattern to detect !command prefix
    SHELL_PATTERN = re.compile(r"^!(.+)$", re.DOTALL)

    def __init__(
        self,
        cwd: Optional[Path] = None,
        timeout: int = 60,
        max_history: int = 100,
        console: Optional[Console] = None,
        permission_callback: Optional[Callable[[str], bool]] = None,
    ):
        """
        Initialize shell runner

        Args:
            cwd: Working directory for commands
            timeout: Default timeout in seconds
            max_history: Maximum history entries to keep
            console: Rich console for output
            permission_callback: Callback to check command permission
        """
        self.cwd = cwd or Path.cwd()
        self.timeout = timeout
        self.max_history = max_history
        self.console = console or Console()
        self.permission_callback = permission_callback
        self._history: list[CommandHistoryEntry] = []

    def is_shell_command(self, text: str) -> bool:
        """
        Check if text is a shell command (starts with !)

        Args:
            text: Input text

        Returns:
            True if text starts with !
        """
        return text.strip().startswith("!")

    def extract_command(self, text: str) -> Optional[str]:
        """
        Extract command from !command syntax

        Args:
            text: Input text with ! prefix

        Returns:
            Command string or None if not a shell command
        """
        match = self.SHELL_PATTERN.match(text.strip())
        if match:
            return match.group(1).strip()
        return None

    def run(
        self,
        command: str,
        show_output: bool = True,
        cwd: Optional[Path] = None,
        timeout: Optional[int] = None,
    ) -> ShellRunnerResult:
        """
        Run a shell command

        Args:
            command: Command to run
            show_output: Show output in terminal
            cwd: Working directory (overrides default)
            timeout: Timeout in seconds (overrides default)

        Returns:
            ShellRunnerResult with execution details
        """
        import subprocess

        if not command.strip():
            return ShellRunnerResult(
                execution=None,
                success=False,
                error="Empty command",
            )

        # SEC-015: Block dangerous commands
        if _is_dangerous_command(command):
            return ShellRunnerResult(
                execution=None,
                success=False,
                error=f"Dangerous command blocked: {mask_secrets(command)}",
            )

        # Check permission if callback provided
        if self.permission_callback and not self.permission_callback(command):
            return ShellRunnerResult(
                execution=None,
                success=False,
                error="Permission denied",
            )

        work_dir = cwd or self.cwd
        cmd_timeout = timeout or self.timeout
        timed_out = False

        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                cwd=str(work_dir),
                timeout=cmd_timeout,
            )
            stdout = result.stdout
            stderr = result.stderr
            exit_code = result.returncode
            success = exit_code == 0

        except subprocess.TimeoutExpired:
            stdout = ""
            stderr = f"Command timed out after {cmd_timeout} seconds"
            exit_code = -1
            success = False
            timed_out = True

        except Exception as e:
            stdout = ""
            stderr = str(e)
            exit_code = -1
            success = False

        # SEC-015: Mask secrets in stored command and output
        execution = CommandExecution(
            command=mask_secrets(command),
            stdout=mask_secrets(stdout),
            stderr=mask_secrets(stderr),
            exit_code=exit_code,
            success=success,
            cwd=str(work_dir),
            timestamp=datetime.now(),
            timed_out=timed_out,
        )

        # Add to history
        self._add_to_history(execution)

        # Show output if requested
        if show_output:
            self._display_output(execution)

        return ShellRunnerResult(
            execution=execution,
            success=success,
        )

    def run_from_input(
        self,
        text: str,
        show_output: bool = True,
        cwd: Optional[Path] = None,
        timeout: Optional[int] = None,
    ) -> ShellRunnerResult:
        """
        Run command from !command input

        Args:
            text: Input text with ! prefix
            show_output: Show output in terminal
            cwd: Working directory
            timeout: Timeout in seconds

        Returns:
            ShellRunnerResult with execution details
        """
        command = self.extract_command(text)
        if not command:
            return ShellRunnerResult(
                execution=None,
                success=False,
                error="Not a shell command",
            )

        return self.run(command, show_output=show_output, cwd=cwd, timeout=timeout)

    def _display_output(self, execution: CommandExecution) -> None:
        """Display command output in terminal"""
        # Build output panel
        output_parts = []

        if execution.stdout:
            output_parts.append(execution.stdout.rstrip())

        if execution.stderr:
            output_parts.append(f"[red]{execution.stderr.rstrip()}[/red]")

        output_text = "\n".join(output_parts) if output_parts else "[dim]No output[/dim]"

        # Determine panel style based on success
        if execution.timed_out:
            border_style = "yellow"
            title = f"⏱️ {execution.command} (timed out)"
        elif execution.success:
            border_style = "green"
            title = f"$ {execution.command}"
        else:
            border_style = "red"
            title = f"$ {execution.command} (exit code {execution.exit_code})"

        panel = Panel(
            output_text,
            title=title,
            title_align="left",
            border_style=border_style,
        )

        self.console.print(panel)

    def _add_to_history(self, execution: CommandExecution) -> None:
        """Add command to history"""
        entry = CommandHistoryEntry(
            command=execution.command,
            timestamp=execution.timestamp,
            success=execution.success,
            exit_code=execution.exit_code,
        )
        self._history.append(entry)

        # Trim history if needed
        if len(self._history) > self.max_history:
            self._history = self._history[-self.max_history :]

    def get_history(self, limit: Optional[int] = None) -> list[CommandHistoryEntry]:
        """
        Get command history

        Args:
            limit: Maximum entries to return (newest first)

        Returns:
            List of history entries
        """
        history = list(reversed(self._history))
        if limit:
            return history[:limit]
        return history

    def clear_history(self) -> None:
        """Clear command history"""
        self._history.clear()

    def format_for_prompt(
        self,
        execution: CommandExecution,
        include_command: bool = True,
        include_output: bool = True,
        max_output_lines: int = 50,
    ) -> str:
        """
        Format command execution for inclusion in prompt

        Args:
            execution: Command execution to format
            include_command: Include the command itself
            include_output: Include command output
            max_output_lines: Maximum output lines to include

        Returns:
            Formatted string for prompt
        """
        parts = []

        if include_command:
            parts.append(f"$ {execution.command}")

        if include_output:
            output = execution.output
            lines = output.split("\n")

            if len(lines) > max_output_lines:
                lines = lines[:max_output_lines]
                lines.append(f"... ({len(lines)} more lines truncated)")

            parts.extend(lines)

        if not execution.success:
            parts.append(f"(exit code: {execution.exit_code})")

        return "\n".join(parts)

    def run_and_format(
        self,
        command: str,
        show_output: bool = True,
        include_in_prompt: bool = False,
    ) -> ShellRunnerResult:
        """
        Run command and optionally format for prompt inclusion

        Args:
            command: Command to run
            show_output: Show output in terminal
            include_in_prompt: Format result for prompt inclusion

        Returns:
            ShellRunnerResult with included_in_prompt flag
        """
        result = self.run(command, show_output=show_output)

        if include_in_prompt and result.execution:
            result.included_in_prompt = True

        return result


# Convenience functions


def is_shell_command(text: str) -> bool:
    """
    Check if text is a shell command (convenience function)

    Args:
        text: Input text

    Returns:
        True if starts with !
    """
    return text.strip().startswith("!")


def run_shell_command(
    command: str,
    cwd: Optional[Path] = None,
    timeout: int = 60,
    show_output: bool = True,
) -> ShellRunnerResult:
    """
    Run a shell command (convenience function)

    Args:
        command: Command to run
        cwd: Working directory
        timeout: Timeout in seconds
        show_output: Show output in terminal

    Returns:
        ShellRunnerResult
    """
    runner = ShellRunner(cwd=cwd, timeout=timeout)
    return runner.run(command, show_output=show_output)


def run_from_input(
    text: str,
    cwd: Optional[Path] = None,
    timeout: int = 60,
    show_output: bool = True,
) -> ShellRunnerResult:
    """
    Run shell command from !command input (convenience function)

    Args:
        text: Input with ! prefix
        cwd: Working directory
        timeout: Timeout in seconds
        show_output: Show output in terminal

    Returns:
        ShellRunnerResult
    """
    runner = ShellRunner(cwd=cwd, timeout=timeout)
    return runner.run_from_input(text, show_output=show_output)
