"""Execute shell commands.

Core system primitive for running shell commands with:
- Timeout enforcement
- Working directory support
- Environment variable support
- Captured stdout/stderr
"""

import asyncio
import os
import time

from core.pathways.context import Context
from core.pathways.models.tool import ToolOutput


async def run(
    command: str,
    cwd: str | None = None,
    timeout: int = 60,
    env: dict[str, str] | None = None,
    context: Context | None = None,
) -> ToolOutput:
    """Execute a shell command.

    Args:
        command: The command to execute (string or list of args)
        cwd: Working directory (default: current directory)
        timeout: Maximum execution time in seconds (default: 60)
        env: Additional environment variables to set
        context: Execution context (injected by framework)

    Returns
    -------
        {
            "success": True/False,
            "exit_code": <return code>,
            "stdout": <captured stdout>,
            "stderr": <captured stderr>,
            "error": <error message if failed>,
            "duration_ms": <execution time>
        }
    """
    if not command:
        return ToolOutput(
            success=False,
            error="command is required",
            data={"exit_code": -1, "stdout": "", "stderr": "", "duration_ms": 0},
        )

    # Resolve working directory
    working_dir = cwd
    if working_dir:
        working_dir = os.path.expanduser(os.path.expandvars(working_dir))
        if not os.path.isabs(working_dir):
            # Make relative to workspace root if available
            workspace = None
            if context:
                workspace = context.run.workspace_root
            if workspace:
                working_dir = os.path.join(workspace, working_dir)
        if not os.path.exists(working_dir):
            return ToolOutput(
                success=False,
                error=f"Working directory does not exist: {working_dir}",
                data={"exit_code": -1, "stdout": "", "stderr": "", "duration_ms": 0},
            )

    # Build environment
    run_env = os.environ.copy()
    if env:
        run_env.update(env)

    start_time = time.time()

    try:
        # Run the command asynchronously
        process = await asyncio.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            cwd=working_dir,
            env=run_env,
        )

        try:
            stdout, stderr = await asyncio.wait_for(
                process.communicate(),
                timeout=timeout,
            )
        except TimeoutError:
            # Kill the process on timeout
            process.kill()
            await process.wait()
            duration_ms = int((time.time() - start_time) * 1000)
            return ToolOutput(
                success=False,
                error=f"Command timed out after {timeout}s",
                data={
                    "exit_code": -1,
                    "stdout": "",
                    "stderr": "",
                    "duration_ms": duration_ms,
                },
            )

        duration_ms = int((time.time() - start_time) * 1000)
        exit_code = process.returncode

        # Decode output
        stdout_str = stdout.decode("utf-8", errors="replace") if stdout else ""
        stderr_str = stderr.decode("utf-8", errors="replace") if stderr else ""

        # Truncate very large outputs
        max_output = 100_000  # 100KB per stream
        if len(stdout_str) > max_output:
            stdout_str = (
                stdout_str[:max_output] + f"\n... (truncated, {len(stdout_str)} bytes total)"
            )
        if len(stderr_str) > max_output:
            stderr_str = (
                stderr_str[:max_output] + f"\n... (truncated, {len(stderr_str)} bytes total)"
            )

        ok = exit_code == 0
        return ToolOutput(
            success=ok,
            error=(None if ok else f"Command failed with exit code {exit_code}"),
            data={
                "exit_code": int(exit_code or 0),
                "stdout": stdout_str,
                "stderr": stderr_str,
                "duration_ms": duration_ms,
            },
        )

    except FileNotFoundError as e:
        duration_ms = int((time.time() - start_time) * 1000)
        return ToolOutput(
            success=False,
            error=f"Command not found: {e}",
            data={"exit_code": -1, "stdout": "", "stderr": "", "duration_ms": duration_ms},
        )
    except PermissionError as e:
        duration_ms = int((time.time() - start_time) * 1000)
        return ToolOutput(
            success=False,
            error=f"Permission denied: {e}",
            data={"exit_code": -1, "stdout": "", "stderr": "", "duration_ms": duration_ms},
        )
    except Exception as e:
        duration_ms = int((time.time() - start_time) * 1000)
        return ToolOutput(
            success=False,
            error=str(e),
            data={"exit_code": -1, "stdout": "", "stderr": "", "duration_ms": duration_ms},
        )
