#!/usr/bin/env python3
"""
Neinteraktivní runner pro CyberBrain26 SAFE/FULL.

Úmyslně podporuje jen bash/sh a python. Spouští kód v pevně určeném
workspace, s omezeným prostředím, timeoutem a zachycením výstupu.
"""

from __future__ import annotations

import os
import shutil
import subprocess
from pathlib import Path
from typing import Any

PROJECT_ROOT = Path(__file__).resolve().parent.parent
# CB26_RUNNER_RUNTIME_WORKSPACE_V1
# Spouštěný kód nesmí používat pracovní složku aplikace ani její data.
from cyberbrain26.paths import project_root as _project_root_fn

WORKSPACE = Path(
    os.environ.get(
        "CB26_RUNNER_WORKSPACE",
        str(_project_root_fn() / "runner-workspace"),
    )
)
WORKSPACE.mkdir(mode=0o700, parents=True, exist_ok=True)
TIMEOUT_SECONDS = 30
MAX_OUTPUT_CHARS = 100_000
ALLOWED_LANGUAGES = {"bash", "sh", "shell", "python", "py"}


def _clip(value: str) -> str:
    if len(value) <= MAX_OUTPUT_CHARS:
        return value
    return value[:MAX_OUTPUT_CHARS] + "\n[output zkrácen]"


def _safe_environment() -> dict[str, str]:
    path = os.pathsep.join(
        directory
        for directory in ("/usr/local/bin", "/usr/bin", "/bin")
        if Path(directory).is_dir()
    )
    return {
        "HOME": str(WORKSPACE),
        "PATH": path,
        "LANG": "C.UTF-8",
        "LC_ALL": "C.UTF-8",
        "PYTHONUNBUFFERED": "1",
    }


def _command_for(language: str, code: str) -> list[str]:
    normalized = language.strip().lower()
    if normalized in {"bash", "sh", "shell"}:
        shell = shutil.which("bash") or "/bin/sh"
        return [shell, "-c", code]
    if normalized in {"python", "py"}:
        python = shutil.which("python3") or "/usr/bin/python3"
        return [python, "-I", "-c", code]
    raise ValueError(f"Nepovolený jazyk: {language}")


def run_approved_code(code: str, language: str) -> dict[str, Any]:
    if not isinstance(code, str) or not code.strip():
        return {
            "ok": False,
            "status": "rejected",
            "reason": "Prázdný kód nelze spustit.",
        }

    normalized = (language or "").strip().lower()
    if normalized not in ALLOWED_LANGUAGES:
        return {
            "ok": False,
            "status": "rejected",
            "reason": f"Nepovolený jazyk: {language or 'neuveden'}.",
            "allowed_languages": sorted(ALLOWED_LANGUAGES),
        }

    WORKSPACE.mkdir(mode=0o700, parents=True, exist_ok=True)

    try:
        completed = subprocess.run(
            _command_for(normalized, code),
            cwd=WORKSPACE,
            env=_safe_environment(),
            stdin=subprocess.DEVNULL,
            capture_output=True,
            text=True,
            timeout=TIMEOUT_SECONDS,
            check=False,
        )
        return {
            "ok": completed.returncode == 0,
            "status": "completed" if completed.returncode == 0 else "failed",
            "language": normalized,
            "returncode": completed.returncode,
            "stdout": _clip(completed.stdout or ""),
            "stderr": _clip(completed.stderr or ""),
            "timeout_seconds": TIMEOUT_SECONDS,
            "workspace": str(WORKSPACE),
        }
    except subprocess.TimeoutExpired as exc:
        return {
            "ok": False,
            "status": "timeout",
            "language": normalized,
            "stdout": _clip((exc.stdout or "") if isinstance(exc.stdout, str) else ""),
            "stderr": _clip((exc.stderr or "") if isinstance(exc.stderr, str) else ""),
            "timeout_seconds": TIMEOUT_SECONDS,
            "workspace": str(WORKSPACE),
        }
    except OSError as exc:
        return {
            "ok": False,
            "status": "error",
            "reason": str(exc),
            "language": normalized,
            "workspace": str(WORKSPACE),
        }
