"""
GSD-style shell execution tool.

Design decisions (per user requirements):
- Direct execution in project directory (no sandbox)
- Configurable timeout (default 120s)
- Fail on non-zero exit code, but agent sees output
- No command whitelist - trust the agent
- Uses AnyIO for async subprocess with proper timeout handling
"""

import anyio
from dataclasses import dataclass
from typing import Optional
from pathlib import Path

from rlm_toolkit.tools import Tool


@dataclass
class ShellResult:
    """Result of a shell command execution."""

    exit_code: int
    stdout: str
    stderr: str
    command: str
    timed_out: bool = False
    workdir: str = ""

    @property
    def success(self) -> bool:
        """Check if command succeeded."""
        return self.exit_code == 0 and not self.timed_out

    @property
    def output(self) -> str:
        """Combined stdout + stderr for display."""
        if self.stdout and self.stderr:
            return f"{self.stdout}\n{self.stderr}"
        return self.stdout or self.stderr

    def __str__(self) -> str:
        """Human-readable result."""
        if self.timed_out:
            return f"Command timed out after {self.workdir}:\n{self.command}"

        status = "✓" if self.success else f"✗ (exit {self.exit_code})"
        return f"{status}\n{self.output}"


class GSDShellTool(Tool):
    """GSD-style shell execution: direct, shows output, fails on error (INT-12, INT-13)."""

    name = "shell"
    description = (
        "Execute a shell command in the project directory. "
        "Use for tests, builds, git operations, and other CLI tools. "
        "Commands run directly without sandbox - use responsibly."
    )

    def __init__(
        self,
        workdir: Path,
        timeout: int = 120,
        fail_on_error: bool = True,
    ):
        """
        Initialize shell tool.

        Args:
            workdir: Working directory for command execution
            timeout: Timeout in seconds (default 120)
            fail_on_error: Whether to raise on non-zero exit (default True)
        """
        self.workdir = Path(workdir).resolve()
        self.timeout = timeout
        self.fail_on_error = fail_on_error

    def run(self, command: str) -> str:
        """Execute shell command.

        Args:
            command: Shell command to execute

        Returns:
            Command output (stdout + stderr) or error message
        """
        # Run async command in sync context
        result = anyio.run(self._run_async, command, None)

        # Format output for agent
        if result.timed_out:
            return f"Command timed out after {self.timeout} seconds:\n{command}"

        output = result.output.strip()

        if result.success:
            return output if output else "Command completed successfully (no output)"
        else:
            # Include exit code in output
            header = f"Command failed with exit code {result.exit_code}:\n"
            return header + (output if output else "(no output)")

    async def _run_async(
        self,
        command: str,
        timeout: Optional[int] = None,
    ) -> ShellResult:
        """Execute command asynchronously with timeout.

        Args:
            command: Shell command to execute
            timeout: Optional timeout override

        Returns:
            ShellResult with execution details
        """
        timeout = timeout or self.timeout

        try:
            # Use anyio.fail_after for proper timeout handling
            # Note: In AnyIO, passing a string command automatically uses shell
            with anyio.fail_after(timeout):
                result = await anyio.run_process(
                    command,
                    cwd=self.workdir,
                    check=False,  # Don't raise on non-zero exit
                )

                return ShellResult(
                    exit_code=result.returncode,
                    stdout=result.stdout.decode("utf-8", errors="replace"),
                    stderr=result.stderr.decode("utf-8", errors="replace"),
                    command=command,
                    timed_out=False,
                    workdir=str(self.workdir),
                )

        except TimeoutError:
            return ShellResult(
                exit_code=-1,
                stdout="",
                stderr=f"Command timed out after {timeout} seconds",
                command=command,
                timed_out=True,
                workdir=str(self.workdir),
            )

        except Exception as e:
            return ShellResult(
                exit_code=-1,
                stdout="",
                stderr=f"Error executing command: {e}",
                command=command,
                timed_out=False,
                workdir=str(self.workdir),
            )

    def run_sync(self, command: str, timeout: Optional[int] = None) -> ShellResult:
        """Synchronous execution returning ShellResult object.

        Use this when you need the structured result instead of string output.

        Args:
            command: Shell command to execute
            timeout: Optional timeout override

        Returns:
            ShellResult with full execution details
        """
        return anyio.run(self._run_async, command, timeout)

    def with_timeout(self, timeout: int) -> "GSDShellTool":
        """Create a copy with different timeout.

        Args:
            timeout: New timeout in seconds

        Returns:
            New GSDShellTool instance with specified timeout
        """
        return GSDShellTool(
            workdir=self.workdir,
            timeout=timeout,
            fail_on_error=self.fail_on_error,
        )
