# adapters/daytona.py

from __future__ import annotations
from typing import Any
from ..core.types import ExecResult, ExecOpts
from ..core.shell import shell_escape, fire_and_forget


def daytona(sandbox: Any) -> _DaytonaAdapter:
    return _DaytonaAdapter(sandbox)


class _DaytonaAdapter:
    def __init__(self, sandbox: Any) -> None:
        self._sb = sandbox

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

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

            # Known limitation: Daytona SDK mixes stdout and stderr into
            # result.result. stderr is not available separately. The returned
            # ExecResult.stdout contains the combined output, stderr is "".
            result = await self._sb.process.execute_command(command, opts.cwd)
            return ExecResult(
                stdout=result.result or "",
                stderr="",
                exit_code=result.exit_code,
            )
        except Exception as e:
            return ExecResult(
                stdout="",
                stderr=str(e),
                exit_code=getattr(e, "exit_code", 1),
            )

    async def write_file(self, path: str, content: str | bytes, *, sudo: bool = False) -> None:
        data = content if isinstance(content, bytes) else content.encode()
        await self._sb.fs.upload_file(data, path)

    async def read_file(self, path: str) -> str:
        content: str = await self._sb.fs.download_file(path)
        return content

    async def stop(self) -> None:
        # Daytona sandbox lifecycle is managed by the Daytona client,
        # not the sandbox object. This is a no-op. The caller must
        # call client.remove(sandbox) separately if full teardown is needed.
        pass

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