"""Sandbox process start/stop/alive abstractions."""

from __future__ import annotations

import logging
import shlex
from dataclasses import dataclass
from typing import Any, Optional

logger: logging.Logger = logging.getLogger(__name__)


@dataclass
class SandboxProcessHandle:
    raw_handle: Any
    command: str
    workdir: str
    script_rel: str


class SandboxProcessManager:
    @staticmethod
    def start_background_python(
        sandbox: Any,
        *,
        workdir: str,
        script_rel: str,
        envs: Optional[dict[str, str]] = None,
        stderr_path: str = "/workspace/.worker_stderr.log",
        args: Optional[list[str]] = None,
    ) -> SandboxProcessHandle:
        envs = envs or {}
        argv = ["python", "-u", script_rel]
        if args:
            argv.extend([str(v) for v in args])

        workdir_literal = shlex.quote(workdir)
        stderr_literal = shlex.quote(stderr_path)
        python_cmd = " ".join(shlex.quote(v) for v in argv)
        shell_cmd = (
            f"cd {workdir_literal} && "
            "source /workspace/.venv/bin/activate && "
            f"exec {python_cmd} 2>{stderr_literal}"
        )
        command = f"bash -lc {shlex.quote(shell_cmd)}"
        raw_handle = sandbox.commands.run(
            command,
            timeout=30,
            envs=envs,
            background=True,
        )
        if raw_handle is None:
            raise RuntimeError(
                f"background process start returned empty handle: {script_rel}"
            )
        return SandboxProcessHandle(
            raw_handle=raw_handle,
            command=command,
            workdir=workdir,
            script_rel=script_rel,
        )

    @staticmethod
    def stop_process(process: Optional[SandboxProcessHandle]) -> None:
        if not process:
            return
        raw = process.raw_handle
        for name in ("terminate", "kill", "stop", "cancel", "close"):
            fn = getattr(raw, name, None)
            if callable(fn):
                try:
                    fn()
                except Exception:
                    logger.debug(
                        "background handle %s.%s failed",
                        type(raw).__name__,
                        name,
                        exc_info=True,
                    )
                return
        logger.warning(
            "background handle does not expose terminate/kill for %s",
            process.script_rel,
        )

    @staticmethod
    def is_process_alive(
        process: Optional[SandboxProcessHandle],
    ) -> bool:
        if process is None:
            return False
        raw = process.raw_handle
        try:
            is_running = getattr(raw, "is_running", None)
            if callable(is_running):
                return bool(is_running())
            if isinstance(is_running, bool):
                return is_running

            done = getattr(raw, "done", None)
            if callable(done):
                return not bool(done())
            if isinstance(done, bool):
                return not done

            poll = getattr(raw, "poll", None)
            if callable(poll):
                return poll() is None

            returncode = getattr(raw, "returncode", None)
            if returncode is not None:
                return False

            status = getattr(raw, "status", None)
            if isinstance(status, str):
                return status.lower() in {"running", "pending", "starting"}

            state = getattr(raw, "state", None)
            if isinstance(state, str):
                return state.lower() in {"running", "pending", "starting"}

            return True
        except Exception:
            return True

    @staticmethod
    def describe_process(process: Optional[SandboxProcessHandle]) -> str:
        if process is None:
            return "none"
        raw = process.raw_handle
        status = "unknown"
        for key in ("status", "state"):
            value = getattr(raw, key, None)
            if isinstance(value, str) and value:
                status = value
                break
        return f"{process.script_rel}@{process.workdir} ({status})"
