# mypy: disable-error-code="list-item"
"""Runloop adapter — wraps a Runloop Devbox into a SandboxAdapter.

Runloop devboxes run as a non-root user (``runloop``) with passwordless
sudo. Use ``runloop_defaults()`` for security configuration optimized
for Runloop's environment (seccomp + unix_sockets + FUSE deferred + cgroups).
"""
from __future__ import annotations

from typing import Any

from ..core.shell import shell_escape, env_prefix, fire_and_forget
from ..core.types import (
    ApprovalsConfig,
    AuditConfig,
    CgroupsConfig,
    DlpConfig,
    DlpCustomPattern,
    DevelopmentConfig,
    ExecOpts,
    ExecResult,
    FileMonitorConfig,
    FuseConfig,
    GrpcConfig,
    HealthConfig,
    LoggingConfig,
    MetricsConfig,
    NetworkInterceptConfig,
    ProxyConfig,
    PtraceConfig,
    SandboxLimitsConfig,
    SeccompDetailsConfig,
    SecureConfig,
    ServerConfig,
    SessionsConfig,
    UnixSocketsConfig,
)
from ..policies.schema import (
    AuditSettings,
    EnvPolicy,
    PolicyDefinition,
    ResourceLimits,
)


def runloop(devbox: Any) -> _RunloopAdapter:
    """Wrap a Runloop Devbox into a SandboxAdapter.

    ``devbox`` should be an object with ``client`` (a Runloop API client)
    and ``id`` (the devbox ID). Typed as Any to avoid a hard dependency
    on the Runloop SDK.

    Usage::

        from runloop_api_client import Runloop
        client = Runloop()
        devbox = await client.devboxes.create_and_await_running(blueprint_id="my-bp")
        adapter = runloop({"client": client, "id": devbox.id})
    """
    return _RunloopAdapter(devbox)


def runloop_defaults() -> SecureConfig:
    """Return Runloop-optimized defaults for SecureConfig.

    Key characteristics:
    - seccomp + unix_sockets enabled (atomic syscall interception)
    - FUSE deferred (chmod'd via sudo on first session start)
    - cgroups enabled for resource isolation
    - ptrace disabled (unix_sockets wrapper provides enforcement)
    - DLP with custom patterns for Runloop API keys and common secrets
    """
    server_config = ServerConfig(
        grpc=GrpcConfig(addr="127.0.0.1:9090"),
        logging=LoggingConfig(level="info", format="text", output="stderr"),
        sessions=SessionsConfig(
            default_timeout="1h",
            idle_timeout="15m",
            cleanup_interval="5m",
        ),
        audit=AuditConfig(enabled=True, sqlite_path="/var/lib/agentsh/events.db"),
        sandbox_limits=SandboxLimitsConfig(
            max_memory_mb=4096, max_cpu_percent=100, max_processes=256,
        ),
        allow_degraded=True,
        fuse=FuseConfig(
            deferred=True,
            deferred_enable_command=["sudo", "/bin/chmod", "666", "/dev/fuse"],
        ),
        network_intercept=NetworkInterceptConfig(
            intercept_mode="all", proxy_listen_addr="127.0.0.1:0",
        ),
        seccomp_details=SeccompDetailsConfig(
            file_monitor=FileMonitorConfig(enabled=True, enforce_without_fuse=True),
        ),
        cgroups=CgroupsConfig(enabled=True),
        unix_sockets=UnixSocketsConfig(enabled=True),
        ptrace=PtraceConfig(enabled=False),
        env_inject={"BASH_ENV": "/usr/lib/agentsh/bash_startup.sh"},
        proxy=ProxyConfig(
            mode="embedded",
            port=0,
            providers={
                "anthropic": "https://api.anthropic.com",
                "openai": "https://api.openai.com",
            },
        ),
        dlp=DlpConfig(
            mode="redact",
            patterns={"email": True, "phone": True, "credit_card": True, "ssn": True, "api_keys": True},
            custom_patterns=[
                DlpCustomPattern(name="runloop_key", display="RUNLOOP_KEY", regex=r"rl_[a-zA-Z0-9]{32,}"),
                DlpCustomPattern(name="openai_key", display="OPENAI_KEY", regex=r"sk-[a-zA-Z0-9]{48,}"),
                DlpCustomPattern(name="anthropic_key", display="ANTHROPIC_KEY", regex=r"sk-ant-[a-zA-Z0-9-]{95,}"),
                DlpCustomPattern(name="aws_access_key", display="AWS_KEY", regex=r"AKIA[0-9A-Z]{16}"),
                DlpCustomPattern(name="github_pat", display="GITHUB_TOKEN", regex=r"ghp_[a-zA-Z0-9]{36}"),
                DlpCustomPattern(name="github_oauth", display="GITHUB_OAUTH", regex=r"gho_[a-zA-Z0-9]{36}"),
                DlpCustomPattern(name="jwt_token", display="JWT", regex=r"eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*"),
                DlpCustomPattern(name="private_key", display="PRIVATE_KEY", regex=r"-----BEGIN [A-Z]+ PRIVATE KEY-----"),
                DlpCustomPattern(name="slack_token", display="SLACK_TOKEN", regex=r"xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}"),
            ],
        ),
        approvals=ApprovalsConfig(enabled=False),
        metrics=MetricsConfig(enabled=True, path="/metrics"),
        health=HealthConfig(path="/health", readiness_path="/ready"),
        development=DevelopmentConfig(disable_auth=True, verbose_errors=False),
    )

    policy = PolicyDefinition(
        file=[
            {"deny": ["/etc/hostname", "/etc/hosts"], "ops": ["write", "create", "delete"]},
            {"deny": [
                "~/.bashrc", "~/.bash_profile", "~/.profile", "~/.bash_login", "~/.bash_logout",
                "~/.zshrc", "~/.zprofile",
                "/root/.bashrc", "/root/.bash_profile", "/root/.profile",
                "/etc/bash.bashrc", "/etc/profile", "/etc/profile.d/**",
            ], "ops": ["write", "create", "delete"]},
            {"allow": [
                "~/.bashrc", "~/.bash_profile", "~/.profile", "~/.bash_login", "~/.bash_logout",
                "~/.zshrc", "~/.zprofile",
            ], "ops": ["read", "open", "stat"]},
            {"allow": ["/workspace", "/workspace/**"], "ops": ["read", "open", "stat", "list", "readlink"]},
            {"allow": ["/workspace", "/workspace/**"], "ops": ["write", "create", "mkdir", "chmod", "rename"]},
            {"soft_delete": ["/workspace", "/workspace/**"]},
            {"allow": ["/tmp/**", "/var/tmp/**"]},
            {"allow": [
                "/dev/null", "/dev/zero", "/dev/urandom", "/dev/random",
                "/dev/stdin", "/dev/stdout", "/dev/stderr",
                "/dev/fd/**", "/dev/pts/**", "/dev/tty", "/dev/fuse",
            ]},
            {"allow": ["/home", "/home/**"], "ops": ["read", "open", "stat", "list", "readlink"]},
            {"allow": [
                "/etc/agentsh/**", "/var/lib/agentsh/**",
                "/var/log/agentsh/**", "/usr/lib/agentsh/**",
            ], "ops": ["read", "open", "stat", "list"]},
            {"allow": ["/proc/self/**", "/proc/version", "/proc/filesystems", "/proc/mounts"], "ops": ["read", "open", "stat", "list"]},
            {"deny": [
                "/usr/bin/sudo", "/usr/bin/su", "/usr/bin/pkexec", "/usr/bin/doas",
                "/bin/su", "/usr/sbin/chroot", "/usr/bin/nsenter", "/usr/bin/unshare",
            ]},
            {"deny": [
                "/var/run/docker.sock", "/run/docker.sock",
                "/var/run/containerd/**", "/run/containerd/**",
            ]},
            {"allow": ["/usr/**", "/lib/**", "/lib64/**", "/bin/**", "/sbin/**"], "ops": ["read", "open", "stat", "list", "readlink"]},
            {"allow": [
                "/etc/hosts", "/etc/resolv.conf",
                "/etc/ssl/certs/**", "/etc/ca-certificates/**",
                "/etc/localtime", "/etc/passwd", "/etc/group",
                "/etc/nsswitch.conf", "/etc/ld.so.cache", "/etc/ld.so.conf", "/etc/ld.so.conf.d/**",
                "/etc/profile", "/etc/profile.d/**", "/etc/bash.bashrc", "/etc/bash.bash_logout",
                "/etc/environment", "/etc/default/**",
                "/etc/gitconfig", "/etc/git/**",
            ], "ops": ["read", "open", "stat", "list"]},
            {"deny": ["~/.ssh/**", "/root/.ssh/**"]},
            {"deny": ["~/.aws/**", "/root/.aws/**"]},
            {"deny": ["~/.gcloud/**", "~/.azure/**", "~/.config/gcloud/**", "~/.kube/**"]},
            {"deny": ["**/.env", "**/.env.*"]},
            {"deny": ["~/.git-credentials", "**/.netrc"]},
            {"allow": [
                "~/.npm/**", "~/.cache/**", "~/.cargo/**",
                "/root/.npm/**", "/root/.cache/**", "/root/.cargo/**",
            ], "ops": ["read", "open", "stat", "list"]},
            {"deny": ["/proc/**", "/sys/**"]},
            {"deny": "**"},
        ],
        network=[
            {"allow_cidrs": ["127.0.0.1/32", "::1/128"]},
            {"allow": ["registry.npmjs.org"], "ports": [443]},
            {"allow": ["pypi.org", "files.pythonhosted.org"], "ports": [443]},
            {"allow": ["crates.io", "static.crates.io"], "ports": [443]},
            {"allow": ["proxy.golang.org", "sum.golang.org"], "ports": [443]},
            {"deny": ["metadata.google.internal", "metadata.goog"]},
            {"deny_cidrs": ["169.254.169.254/32", "100.100.100.200/32"]},
            {"deny": ["kubernetes.default.svc", "*.svc.cluster.local"]},
            {"deny_cidrs": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16"]},
            {"deny": "*"},
        ],
        commands=[
            {"allow": ["curl", "wget"]},
            {"allow": [
                "bash", "sh", "/bin/bash", "/bin/sh", "/usr/bin/bash", "/usr/bin/sh",
                "ls", "cat", "head", "tail", "grep", "find", "wc", "sort", "uniq",
                "diff", "pwd", "echo", "date", "which",
            ]},
            {"allow": ["git", "node", "npm", "python", "python3", "pip", "pip3", "cargo", "go", "make"]},
            {"deny": ["nc", "netcat", "ncat", "socat", "telnet", "ssh", "scp", "rsync"]},
            {"deny": [
                "shutdown", "reboot", "systemctl", "service",
                "mount", "umount", "dd", "fdisk", "mkfs",
                "kill", "killall", "pkill",
                "hostname", "domainname", "hostnamectl",
            ]},
            {"deny": ["sudo", "su", "chroot", "nsenter", "unshare", "docker", "podman", "nerdctl"]},
            {"allow": "*"},
        ],
        env_policy=EnvPolicy(
            deny=[
                "*_SECRET*", "*_PASSWORD*", "*_PRIVATE_KEY*", "*_API_KEY*", "*_ACCESS_KEY*", "*_TOKEN",
                "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "RUNLOOP_API_KEY",
                "GITHUB_TOKEN", "GH_TOKEN", "NPM_TOKEN",
                "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
                "GOOGLE_APPLICATION_CREDENTIALS",
            ],
            block_iteration=True,
        ),
        signal_rules=[
            {"name": "allow-self", "signals": ["@all"], "target": {"type": "self"}, "decision": "allow"},
            {"name": "allow-children", "signals": ["@all"], "target": {"type": "children"}, "decision": "allow"},
            {"name": "allow-session", "signals": ["SIGTERM", "SIGINT", "SIGHUP", "SIGUSR1", "SIGUSR2"], "target": {"type": "session"}, "decision": "allow"},
            {"name": "audit-parent", "signals": ["@all"], "target": {"type": "parent"}, "decision": "audit"},
            {"name": "deny-external-fatal", "signals": ["@fatal"], "target": {"type": "external"}, "decision": "deny", "fallback": "audit", "message": "Blocking signal to process outside session"},
            {"name": "deny-system", "signals": ["@all"], "target": {"type": "system"}, "decision": "deny", "fallback": "audit", "message": "Blocking signal to system process"},
        ],
        unix_socket_rules=[
            {"name": "deny-system-sockets", "paths": ["/var/run/**"], "operations": ["connect", "bind", "listen", "sendto"], "decision": "deny"},
        ],
        resource_limits=ResourceLimits(
            max_memory_mb=2048,
            cpu_quota_percent=50,
            pids_max=100,
            command_timeout="5m",
            session_timeout="1h",
            idle_timeout="15m",
        ),
        audit_settings=AuditSettings(
            log_allowed=True,
            log_denied=True,
            log_approved=True,
            include_stdout=True,
            include_stderr=True,
        ),
    )

    return SecureConfig(
        policy=policy,
        install_strategy="download",
        real_paths=True,
        server_config=server_config,
    )


class _RunloopAdapter:
    """Wraps a Runloop Devbox object into SandboxAdapter."""

    def __init__(self, devbox: Any) -> None:
        self._client = devbox["client"] if isinstance(devbox, dict) else devbox.client
        self._id = devbox["id"] if isinstance(devbox, dict) else devbox.id

    async def _run(self, cmd: str) -> ExecResult:
        """Execute a command via the Runloop API."""
        try:
            result = await self._client.devboxes.execute_sync(self._id, command=cmd)
            return ExecResult(
                stdout=getattr(result, "stdout", "") or "",
                stderr=getattr(result, "stderr", "") or "",
                exit_code=getattr(result, "exit_status", 0) or 0,
            )
        except Exception as exc:
            return ExecResult(
                stdout=getattr(exc, "stdout", "") or "",
                stderr=getattr(exc, "stderr", "") or str(exc),
                exit_code=getattr(exc, "exit_status", None)
                or getattr(exc, "exit_code", None)
                or 1,
            )

    async def exec(
        self,
        cmd: str,
        args: list[str] | None = None,
        opts: ExecOpts | None = None,
    ) -> ExecResult:
        opts = opts or ExecOpts()
        command = f"{env_prefix(opts.env)}{'sudo ' if opts.sudo else ''}{shell_escape(cmd, args)}"

        if opts.detached:
            fire_and_forget(self._run(f"nohup {command} > /dev/null 2>&1 &"))
            return ExecResult(stdout="", stderr="", exit_code=0)

        if opts.cwd:
            safe_cwd = opts.cwd.replace("'", "'\\''")
            return await self._run(f"cd '{safe_cwd}' && {command}")

        return await self._run(command)

    async def write_file(
        self,
        path: str,
        content: str | bytes,
        *,
        sudo: bool = False,
    ) -> None:
        try:
            text = content if isinstance(content, str) else content.decode("utf-8")
            await self._client.devboxes.write_file_contents(
                self._id, file_path=path, contents=text,
            )
        except Exception as exc:
            raise Exception(f"writeFile failed: {exc}") from exc

    async def read_file(self, path: str) -> str:
        try:
            result: str = await self._client.devboxes.read_file_contents(
                self._id, file_path=path,
            )
            return result
        except Exception as exc:
            raise Exception(f"readFile failed: {exc}") from exc

    async def stop(self) -> None:
        await self._client.devboxes.shutdown(self._id)

    async def file_exists(self, path: str) -> bool:
        result = await self.exec("test", ["-f", path])
        return result.exit_code == 0
