"""Monza sandbox backend implementation using the Monza HTTP API."""

from __future__ import annotations

import os
from typing import Any

import httpx

from deepagents.backends.protocol import (
    ExecuteResponse,
    FileDownloadResponse,
    FileUploadResponse,
)
from deepagents.backends.sandbox import BaseSandbox


class MonzaSandbox(BaseSandbox):
    """Monza sandbox implementation conforming to SandboxBackendProtocol.

    This implementation talks to the Monza Go backend over HTTP and delegates
    file operations and command execution to it.

    All BaseSandbox methods are usable: ``ls_info``, ``read``, ``write``,
    ``edit``, ``grep_raw``, and ``glob_info`` inherit from BaseSandbox and
    run inside the sandbox via the overridden ``execute()`` (Monza execute API).
    Use ``close()`` when done, or use the instance as a context manager so the
    HTTP client is closed on exit.
    """

    def __init__(
        self,
        *,
        base_url: str = "http://localhost:8080",
        sandbox_id: str | None = None,
        template: str = "python-3.12",
        name: str | None = None,
        timeout: int = 30 * 60,
    ) -> None:
        """Create or attach to a Monza sandbox.

        Args:
            base_url: Base URL of the Monza backend (e.g. \"http://localhost:8080\").
            sandbox_id: Optional existing sandbox id. If provided, this instance
                will attach to that sandbox and validate it exists.
            template: Devcontainer template name to use when creating a sandbox.
            name: Optional human-readable name for a newly created sandbox.
            timeout: Default command timeout in seconds used when ``execute()``
                is called without an explicit ``timeout``.
        """
        self._base_url = base_url.rstrip("/")
        self._default_timeout = timeout
        self._client = httpx.Client(base_url=self._base_url, timeout=self._default_timeout)

        if sandbox_id is not None:
            # Validate the sandbox exists.
            resp = self._client.get(f"/api/sandboxes/{sandbox_id}")
            resp.raise_for_status()
            self._sandbox_id = sandbox_id
        else:
            payload: dict[str, Any] = {"template": template}
            if name:
                payload["name"] = name
            resp = self._client.post("/api/sandboxes", json=payload)
            resp.raise_for_status()
            data = resp.json()
            self._sandbox_id = str(data["id"])

    @property
    def id(self) -> str:
        """Return the Monza sandbox id."""
        return self._sandbox_id

    def execute(
        self,
        command: str,
        *,
        timeout: int | None = None,
    ) -> ExecuteResponse:
        """Execute a shell command inside the sandbox via Monza HTTP API."""
        effective_timeout = timeout if timeout is not None else self._default_timeout
        try:
            resp = self._client.post(
                f"/api/sandboxes/{self._sandbox_id}/execute",
                json={
                    "command": command,
                    "max_output_bytes": 65536,
                },
                timeout=effective_timeout,
            )
            resp.raise_for_status()
            data = resp.json()
            output = data.get("output", "")
            exit_code = data.get("exitCode")
            truncated = bool(data.get("truncated", False))
            return ExecuteResponse(output=output, exit_code=exit_code, truncated=truncated)
        except httpx.HTTPError as exc:
            return ExecuteResponse(
                output=f"HTTP error during execute: {exc}",
                exit_code=None,
                truncated=False,
            )

    def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
        """Download files from the sandbox via Monza HTTP API."""
        responses: list[FileDownloadResponse] = []

        for path in paths:
            if not path.startswith("/"):
                responses.append(
                    FileDownloadResponse(path=path, content=None, error="invalid_path")
                )
                continue

            try:
                resp = self._client.get(
                    f"/api/sandboxes/{self._sandbox_id}/files/download",
                    params={"path": path},
                    timeout=self._default_timeout,
                )
            except httpx.HTTPError as exc:
                responses.append(
                    FileDownloadResponse(
                        path=path,
                        content=None,
                        error="file_not_found",
                    )
                )
                continue

            if resp.status_code == 404:
                responses.append(
                    FileDownloadResponse(
                        path=path,
                        content=None,
                        error="file_not_found",
                    )
                )
                continue

            if resp.is_success:
                responses.append(
                    FileDownloadResponse(
                        path=path,
                        content=resp.content,
                        error=None,
                    )
                )
            else:
                responses.append(
                    FileDownloadResponse(
                        path=path,
                        content=None,
                        error="invalid_path",
                    )
                )

        return responses

    def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
        """Upload files into the sandbox via Monza HTTP API."""
        responses: list[FileUploadResponse] = []

        for destination_path, content in files:
            if not destination_path.startswith("/"):
                responses.append(
                    FileUploadResponse(path=destination_path, error="invalid_path")
                )
                continue

            directory = os.path.dirname(destination_path)
            filename = os.path.basename(destination_path)
            if not directory:
                directory = "/workspace"

            try:
                resp = self._client.post(
                    f"/api/sandboxes/{self._sandbox_id}/files/upload",
                    files={"file": (filename, content)},
                    data={"path": directory},
                    timeout=self._default_timeout,
                )
            except httpx.HTTPError:
                responses.append(
                    FileUploadResponse(path=destination_path, error="invalid_path")
                )
                continue

            if resp.is_success:
                responses.append(FileUploadResponse(path=destination_path, error=None))
            else:
                responses.append(
                    FileUploadResponse(path=destination_path, error="invalid_path")
                )

        return responses

    def delete(self) -> None:
        """Delete the sandbox via Monza HTTP API."""
        try:
            self._client.delete(f"/api/sandboxes/{self._sandbox_id}")
        except httpx.HTTPError:
            # Best-effort; errors are ignored.
            return

    def heartbeat(self) -> None:
        """Send a heartbeat to keep the sandbox alive."""
        try:
            self._client.post(
                f"/api/sandboxes/{self._sandbox_id}/heartbeat",
                timeout=self._default_timeout,
            )
        except httpx.HTTPError:
            return

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> MonzaSandbox:
        """Enter context manager; returns self."""
        return self

    def __exit__(self, *args: object) -> None:
        """Exit context manager; closes the HTTP client."""
        self.close()
