"""
Bash Operations Module

Provides bash command execution with output capture and PTY support.
"""

import os
import pty
import select
import shlex
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Optional


@dataclass
class BashResult:
    """Result of a bash command execution"""

    success: bool
    stdout: str
    stderr: str
    exit_code: int
    command: str
    cwd: Optional[Path] = None


class BashOperations:
    """
    Bash operations for groknroll agent

    Features:
    - Execute shell commands with output capture
    - PTY support for interactive commands
    - Working directory management
    - Environment variable handling
    - Command history and safety checks
    """

    def __init__(self, project_path: Path, default_shell: str = "/bin/bash", timeout: int = 300):
        """
        Initialize bash operations

        Args:
            project_path: Root project path (default working directory)
            default_shell: Shell to use for execution
            timeout: Default command timeout in seconds
        """
        self.project_path = project_path.resolve()
        self.default_shell = default_shell
        self.default_timeout = timeout
        self.command_history: list[BashResult] = []

        # Dangerous commands that require confirmation
        self.dangerous_commands = {
            "rm -rf /",
            "dd if=",
            "mkfs",
            ":(){ :|:& };:",  # Fork bomb
        }

    def execute(
        self,
        command: str,
        cwd: Optional[Path] = None,
        env: Optional[dict[str, str]] = None,
        timeout: Optional[int] = None,
        shell: bool = True,
        capture_output: bool = True,
    ) -> BashResult:
        """
        Execute bash command

        Args:
            command: Command to execute
            cwd: Working directory (defaults to project_path)
            env: Environment variables (merged with os.environ)
            timeout: Command timeout in seconds
            shell: Execute in shell (required for pipes, redirects, etc.)
            capture_output: Capture stdout/stderr

        Returns:
            BashResult
        """
        # Safety check
        if self._is_dangerous(command):
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Dangerous command blocked: {command}",
                exit_code=-1,
                command=command,
                cwd=cwd,
            )

        # Resolve working directory
        work_dir = self._resolve_path(cwd) if cwd else self.project_path

        # Prepare environment
        exec_env = os.environ.copy()
        if env:
            exec_env.update(env)

        # Set timeout
        exec_timeout = timeout or self.default_timeout

        try:
            # Execute command
            if shell:
                result = subprocess.run(
                    command,
                    shell=True,
                    cwd=str(work_dir),
                    env=exec_env,
                    capture_output=capture_output,
                    text=True,
                    timeout=exec_timeout,
                    executable=self.default_shell,
                )
            else:
                # Parse command into args
                args = shlex.split(command)
                result = subprocess.run(
                    args,
                    cwd=str(work_dir),
                    env=exec_env,
                    capture_output=capture_output,
                    text=True,
                    timeout=exec_timeout,
                )

            bash_result = BashResult(
                success=result.returncode == 0,
                stdout=result.stdout if capture_output else "",
                stderr=result.stderr if capture_output else "",
                exit_code=result.returncode,
                command=command,
                cwd=work_dir,
            )

            # Add to history
            self.command_history.append(bash_result)

            return bash_result

        except subprocess.TimeoutExpired:
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Command timed out after {exec_timeout} seconds",
                exit_code=-1,
                command=command,
                cwd=work_dir,
            )
        except Exception as e:
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Error executing command: {e}",
                exit_code=-1,
                command=command,
                cwd=work_dir,
            )

    def execute_interactive(
        self, command: str, cwd: Optional[Path] = None, env: Optional[dict[str, str]] = None
    ) -> BashResult:
        """
        Execute command with PTY (for interactive commands like vim, top, etc.)

        Args:
            command: Command to execute
            cwd: Working directory
            env: Environment variables

        Returns:
            BashResult
        """
        # Safety check
        if self._is_dangerous(command):
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Dangerous command blocked: {command}",
                exit_code=-1,
                command=command,
                cwd=cwd,
            )

        # Resolve working directory
        work_dir = self._resolve_path(cwd) if cwd else self.project_path

        # Prepare environment
        exec_env = os.environ.copy()
        if env:
            exec_env.update(env)

        try:
            # Create PTY
            master, slave = pty.openpty()

            # Execute command in PTY
            process = subprocess.Popen(
                command,
                shell=True,
                cwd=str(work_dir),
                env=exec_env,
                stdin=slave,
                stdout=slave,
                stderr=slave,
                executable=self.default_shell,
            )

            # Close slave (parent doesn't need it)
            os.close(slave)

            # Wait for process to complete
            process.wait()

            # Read output
            output = b""
            while True:
                try:
                    # Use select to check if data is available
                    ready, _, _ = select.select([master], [], [], 0.1)
                    if ready:
                        chunk = os.read(master, 4096)
                        if not chunk:
                            break
                        output += chunk
                    else:
                        break
                except OSError:
                    break

            os.close(master)

            bash_result = BashResult(
                success=process.returncode == 0,
                stdout=output.decode("utf-8", errors="replace"),
                stderr="",
                exit_code=process.returncode,
                command=command,
                cwd=work_dir,
            )

            self.command_history.append(bash_result)

            return bash_result

        except Exception as e:
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Error executing interactive command: {e}",
                exit_code=-1,
                command=command,
                cwd=work_dir,
            )

    def execute_pipeline(
        self,
        commands: list[str],
        cwd: Optional[Path] = None,
        env: Optional[dict[str, str]] = None,
        timeout: Optional[int] = None,
    ) -> BashResult:
        """
        Execute pipeline of commands (command1 | command2 | ...)

        Args:
            commands: List of commands to pipe together
            cwd: Working directory
            env: Environment variables
            timeout: Command timeout

        Returns:
            BashResult
        """
        # Create pipeline command
        pipeline = " | ".join(commands)

        return self.execute(command=pipeline, cwd=cwd, env=env, timeout=timeout, shell=True)

    def execute_script(
        self,
        script_path: Path,
        args: Optional[list[str]] = None,
        cwd: Optional[Path] = None,
        env: Optional[dict[str, str]] = None,
        timeout: Optional[int] = None,
    ) -> BashResult:
        """
        Execute bash script

        Args:
            script_path: Path to script file
            args: Script arguments
            cwd: Working directory
            env: Environment variables
            timeout: Command timeout

        Returns:
            BashResult
        """
        script_file = self._resolve_path(script_path)

        if not script_file.exists():
            return BashResult(
                success=False,
                stdout="",
                stderr=f"Script not found: {script_path}",
                exit_code=-1,
                command=f"bash {script_path}",
                cwd=cwd,
            )

        # Build command
        command_parts = [self.default_shell, str(script_file)]
        if args:
            command_parts.extend(args)

        command = " ".join(shlex.quote(part) for part in command_parts)

        return self.execute(command=command, cwd=cwd, env=env, timeout=timeout, shell=False)

    def get_history(self, limit: int = 10) -> list[BashResult]:
        """Get recent command history"""
        return self.command_history[-limit:]

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

    # =========================================================================
    # Common Commands (Convenience Methods)
    # =========================================================================

    def ls(self, path: Optional[Path] = None, options: str = "-la") -> BashResult:
        """List directory contents"""
        target = self._resolve_path(path) if path else self.project_path
        return self.execute(f"ls {options} {shlex.quote(str(target))}")

    def pwd(self) -> BashResult:
        """Print working directory"""
        return self.execute("pwd", cwd=self.project_path)

    def which(self, command: str) -> BashResult:
        """Find command location"""
        return self.execute(f"which {shlex.quote(command)}")

    def env_vars(self) -> BashResult:
        """List environment variables"""
        return self.execute("env")

    def ps(self, options: str = "aux") -> BashResult:
        """List processes"""
        return self.execute(f"ps {options}")

    def grep(self, pattern: str, path: Optional[Path] = None, options: str = "-r") -> BashResult:
        """Search for pattern in files"""
        target = self._resolve_path(path) if path else self.project_path
        return self.execute(f"grep {options} {shlex.quote(pattern)} {shlex.quote(str(target))}")

    def find(
        self,
        path: Optional[Path] = None,
        name: Optional[str] = None,
        type_filter: Optional[str] = None,
    ) -> BashResult:
        """Find files"""
        target = self._resolve_path(path) if path else self.project_path
        command_parts = ["find", shlex.quote(str(target))]

        if type_filter:
            command_parts.extend(["-type", type_filter])

        if name:
            command_parts.extend(["-name", shlex.quote(name)])

        return self.execute(" ".join(command_parts))

    # =========================================================================
    # Helper Methods
    # =========================================================================

    def _resolve_path(self, path: Path) -> Path:
        """Resolve path relative to project root"""
        if path.is_absolute():
            return path.resolve()
        return (self.project_path / path).resolve()

    def _is_dangerous(self, command: str) -> bool:
        """Check if command is potentially dangerous"""
        # Check against known dangerous patterns
        for dangerous in self.dangerous_commands:
            if dangerous in command:
                return True

        # Additional checks
        dangerous_patterns = [
            "rm -rf /",
            "> /dev/sda",
            "chmod -R 777 /",
        ]

        for pattern in dangerous_patterns:
            if pattern in command:
                return True

        return False

    def test_command_available(self, command: str) -> bool:
        """Test if command is available in PATH"""
        result = self.which(command)
        return result.success

    def get_shell_version(self) -> BashResult:
        """Get shell version"""
        return self.execute(f"{self.default_shell} --version")
