"""
Sandbox — Isolated execution environment for skills.

Runs skill code in subprocess with timeout enforcement, output capture,
and resource limits. This is the enforcement layer for NeuralClaw's
capability-based security model.
"""

from __future__ import annotations

import asyncio
import json
import os
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any


# ---------------------------------------------------------------------------
# Sandbox result
# ---------------------------------------------------------------------------

@dataclass
class SandboxResult:
    """Result from sandboxed execution."""
    success: bool
    output: str
    error: str | None = None
    exit_code: int = 0
    timed_out: bool = False
    execution_time_ms: float = 0.0


# ---------------------------------------------------------------------------
# Sandbox
# ---------------------------------------------------------------------------

class Sandbox:
    """
    Subprocess-based isolated execution environment.

    Runs Python code in a clean subprocess with:
    - Timeout enforcement
    - stdout/stderr capture
    - Limited environment variables
    - Controlled working directory
    """

    def __init__(
        self,
        timeout_seconds: int = 30,
        allowed_dirs: list[str] | None = None,
    ) -> None:
        self._timeout = timeout_seconds
        self._allowed_dirs = allowed_dirs or []

    async def execute_python(
        self,
        code: str,
        working_dir: str | None = None,
    ) -> SandboxResult:
        """Execute Python code in an isolated subprocess."""
        import time as _time

        # Write code to a temporary file
        with tempfile.NamedTemporaryFile(
            mode="w",
            suffix=".py",
            delete=False,
            encoding="utf-8",
        ) as f:
            f.write(code)
            script_path = f.name

        try:
            # Build clean environment
            clean_env = self._build_clean_env()

            cwd = working_dir or tempfile.gettempdir()

            start = _time.time()

            proc = await asyncio.create_subprocess_exec(
                sys.executable, script_path,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                env=clean_env,
                cwd=cwd,
            )

            try:
                stdout, stderr = await asyncio.wait_for(
                    proc.communicate(),
                    timeout=self._timeout,
                )
                elapsed = (_time.time() - start) * 1000

                return SandboxResult(
                    success=proc.returncode == 0,
                    output=stdout.decode("utf-8", errors="replace").strip(),
                    error=stderr.decode("utf-8", errors="replace").strip() or None,
                    exit_code=proc.returncode or 0,
                    execution_time_ms=round(elapsed, 1),
                )

            except asyncio.TimeoutError:
                proc.kill()
                await proc.communicate()
                elapsed = (_time.time() - start) * 1000
                return SandboxResult(
                    success=False,
                    output="",
                    error=f"Execution timed out after {self._timeout}s",
                    exit_code=-1,
                    timed_out=True,
                    execution_time_ms=round(elapsed, 1),
                )

        finally:
            try:
                os.unlink(script_path)
            except OSError:
                pass

    async def execute_command(
        self,
        command: list[str],
        working_dir: str | None = None,
    ) -> SandboxResult:
        """Execute a shell command in an isolated subprocess."""
        import time as _time

        clean_env = self._build_clean_env()
        cwd = working_dir or tempfile.gettempdir()

        start = _time.time()

        proc = await asyncio.create_subprocess_exec(
            *command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            env=clean_env,
            cwd=cwd,
        )

        try:
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=self._timeout,
            )
            elapsed = (_time.time() - start) * 1000

            return SandboxResult(
                success=proc.returncode == 0,
                output=stdout.decode("utf-8", errors="replace").strip(),
                error=stderr.decode("utf-8", errors="replace").strip() or None,
                exit_code=proc.returncode or 0,
                execution_time_ms=round(elapsed, 1),
            )

        except asyncio.TimeoutError:
            proc.kill()
            await proc.communicate()
            elapsed = (_time.time() - start) * 1000
            return SandboxResult(
                success=False,
                output="",
                error=f"Execution timed out after {self._timeout}s",
                exit_code=-1,
                timed_out=True,
                execution_time_ms=round(elapsed, 1),
            )

    def _build_clean_env(self) -> dict[str, str]:
        """Build a minimal, clean environment for subprocess execution."""
        env: dict[str, str] = {}

        # Keep only essential env vars
        for key in ("PATH", "SYSTEMROOT", "TEMP", "TMP", "HOME", "USER", "LANG"):
            val = os.environ.get(key)
            if val:
                env[key] = val

        # Python-specific
        env["PYTHONDONTWRITEBYTECODE"] = "1"
        env["PYTHONUNBUFFERED"] = "1"

        return env
