import asyncio
import shutil
import sys
from pathlib import Path

_IS_LINUX = sys.platform == "linux"
_BWRAP_PATH = shutil.which("bwrap") if _IS_LINUX else None


def bwrap_path() -> str | None:
    """Return path to bwrap binary, or None."""
    return _BWRAP_PATH


def is_available() -> bool:
    """Return True if bwrap is installed and platform is Linux."""
    return _IS_LINUX and _BWRAP_PATH is not None


def _build_bwrap_args(command: str, workspace_root: Path, *, network: bool) -> list[str]:
    workspace = str(workspace_root.resolve())

    args = [
        _BWRAP_PATH or "bwrap",
        "--ro-bind",
        "/usr",
        "/usr",
        "--ro-bind",
        "/bin",
        "/bin",
        "--ro-bind",
        "/lib",
        "/lib",
    ]

    if Path("/lib64").exists():
        args.extend(["--ro-bind", "/lib64", "/lib64"])

    args.extend(
        [
            "--ro-bind",
            "/etc",
            "/etc",
            "--proc",
            "/proc",
            "--dev",
            "/dev",
            "--tmpfs",
            "/tmp",
            "--bind",
            workspace,
            workspace,
            "--chdir",
            workspace,
            "--unshare-pid",
            "--die-with-parent",
        ]
    )

    if not network:
        args.append("--unshare-net")

    args.extend(["--", "bash", "-c", command])
    return args


async def sandboxed_exec(
    command: str,
    workspace_root: Path,
    *,
    timeout: int = 120,
    network: bool = True,
) -> tuple[str, int]:
    """Run command in bwrap sandbox. Returns (output, return_code)."""
    if not _IS_LINUX:
        return "Sandbox is only available on Linux", 127

    if _BWRAP_PATH is None:
        return "bubblewrap (bwrap) is not installed", 127

    try:
        proc = await asyncio.create_subprocess_exec(
            *_build_bwrap_args(command, workspace_root, network=network),
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
    except Exception as exc:
        return f"Failed to start sandboxed command: {exc}", 1

    try:
        stdout, stderr = await asyncio.wait_for(
            proc.communicate(), timeout=max(1, int(timeout or 120))
        )
    except TimeoutError:
        proc.kill()
        await proc.wait()
        return f"Command timed out after {timeout} seconds", 124

    output = ((stdout or b"") + (stderr or b"")).decode("utf-8", errors="replace")
    if not output:
        output = "(no output)"

    return output, int(proc.returncode or 0)
