#!/usr/bin/env bash
set -euo pipefail

die() {
  echo "$*" >&2
  exit 1
}

warn() {
  echo "AVISO: $*" >&2
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1
}

valid_user() {
  [[ "$1" =~ ^[a-z_][a-z0-9_-]{1,31}$ ]]
}

valid_version() {
  [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
}

valid_minor_version() {
  [[ "$1" =~ ^[0-9]+\.[0-9]+$ ]]
}

valid_python_runtime_version() {
  [[ "$1" =~ ^3\.(11|12|13)$ ]]
}

valid_package() {
  [[ "$1" =~ ^[A-Za-z][A-Za-z0-9.]*$ ]]
}

valid_python_package() {
  [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,213}$ ]]
}

valid_node() {
  [[ "$1" =~ ^[A-Za-z0-9_.:-]{1,255}$ ]]
}

valid_python_env_root() {
  [[ "$1" == /* ]] || return 1
  [[ "$1" != *".."* ]] || return 1
  [[ "$1" == */python-envs || "$1" == */python-envs/* ]]
}

valid_python_runtime_root() {
  [[ "$1" == /* ]] || return 1
  [[ "$1" != *".."* ]] || return 1
  [[ "$1" == */python || "$1" == */python-runtimes || "$1" == */python-runtimes/* ]]
}

valid_config_file() {
  [[ "$1" == /etc/solar-workbench.conf || "$1" == /etc/solar-workbench-cluster.conf || "$1" == /etc/devra-ide.env || "$1" == /etc/solar-workbench-cluster.env ]]
}

normalize_node_value() {
  local value="$1"
  value="${value%\\}"
  printf '%s' "$value"
}

valid_cluster_nodes() {
  local nodes="$1"
  local node
  local -a node_list
  [[ -n "$nodes" ]] || return 1
  IFS=',' read -r -a node_list <<< "$nodes"
  for node in "${node_list[@]}"; do
    node="${node// /}"
    node="$(normalize_node_value "$node")"
    [[ -n "$node" ]] || return 1
    valid_node "$node" || return 1
  done
}

valid_home_relative() {
  local path="$1"
  [[ -n "$path" ]] || return 1
  [[ "$path" != /* ]] || return 1
  [[ "$path" != "." ]] || return 1
  [[ "$path" != *".."* ]] || return 1
}

session_target_path() {
  local home="$1"
  local relative="$2"
  [[ "$home" == /* ]] || die "Home no valida"
  valid_home_relative "$relative" || die "Ruta relativa no valida"
  python3 - "$home" "$relative" <<'PY'
import sys
from pathlib import Path

home = Path(sys.argv[1]).resolve()
target = (home / sys.argv[2]).resolve()
if target != home and home not in target.parents:
    raise SystemExit(2)
print(target)
PY
}

ensure_home_parent_access() {
  local home="$1"
  local group="$2"
  local parent
  parent="$(dirname "$home")"
  mkdir -p "$parent"
  chgrp "$group" "$parent" 2>/dev/null || true
  chmod 0710 "$parent" 2>/dev/null || true
}

valid_sync_command() {
  [[ "$1" == "push" || "$1" == "bidirectional" || "$1" == "delete-user" || "$1" == "delete-package" || "$1" == "delete-python-package" || "$1" == "delete-home-path" || "$1" == "delete-workspace-path" || "$1" == "set-nodes" ]]
}

PKG_MANAGER=""
PYTHON_RUNTIME_ROOT="${DEVRA_PYTHON_RUNTIME_ROOT:-/opt/solar-workbench/python}"

detect_package_manager() {
  if need_cmd apt-get; then
    PKG_MANAGER="apt"
  elif need_cmd dnf; then
    PKG_MANAGER="dnf"
  elif need_cmd yum; then
    PKG_MANAGER="yum"
  elif need_cmd zypper; then
    PKG_MANAGER="zypper"
  else
    PKG_MANAGER="generic"
    warn "No se ha encontrado apt-get, dnf, yum ni zypper. Se continuara en modo generico si las dependencias ya existen."
  fi
}

pkg_update() {
  case "$PKG_MANAGER" in
    apt)
      export DEBIAN_FRONTEND=noninteractive
      apt-get update
      ;;
    dnf)
      dnf -y makecache || true
      ;;
    yum)
      yum -y makecache || true
      ;;
    zypper)
      zypper --non-interactive refresh || true
      ;;
    generic)
      ;;
  esac
}

pkg_install() {
  case "$PKG_MANAGER" in
    apt)
      export DEBIAN_FRONTEND=noninteractive
      apt-get install -y --no-install-recommends "$@"
      ;;
    dnf)
      dnf install -y "$@"
      ;;
    yum)
      yum install -y "$@"
      ;;
    zypper)
      zypper --non-interactive install --no-recommends "$@"
      ;;
    generic)
      warn "Modo generico: instala manualmente estos paquetes si faltan: $*"
      return 1
      ;;
  esac
}

pkg_install_optional() {
  local pkg
  for pkg in "$@"; do
    if ! pkg_install "$pkg"; then
      warn "No se pudo instalar el paquete opcional '$pkg'. Se continuará con la compilación."
    fi
  done
}

system_dep_packages() {
  local dep="$1"
  case "$PKG_MANAGER" in
    apt)
      case "$dep" in
        cairo) echo "libcairo2-dev" ;;
        cmake) echo "cmake" ;;
        curl) echo "libcurl4-openssl-dev" ;;
        fontconfig) echo "libfontconfig1-dev" ;;
        freetype) echo "libfreetype6-dev" ;;
        fribidi) echo "libfribidi-dev" ;;
        gdal) echo "libgdal-dev" ;;
        geos) echo "libgeos-dev" ;;
        git) echo "git" ;;
        harfbuzz) echo "libharfbuzz-dev" ;;
        jpeg) echo "libjpeg-dev" ;;
        libgit2) echo "libgit2-dev" ;;
        libuv) echo "libuv1-dev" ;;
        glu) echo "libglu1-mesa-dev" ;;
        make) echo "make" ;;
        opengl) echo "libgl1-mesa-dev mesa-common-dev" ;;
        openssl) echo "libssl-dev" ;;
        pango) echo "libpango1.0-dev" ;;
        png) echo "libpng-dev" ;;
        proj) echo "libproj-dev proj-bin" ;;
        sqlite) echo "libsqlite3-dev" ;;
        tiff) echo "libtiff-dev" ;;
        udunits2) echo "libudunits2-dev" ;;
        v8) echo "libv8-dev" ;;
        xml2) echo "libxml2-dev" ;;
        x11) echo "libx11-dev libxext-dev libxrender-dev libxt-dev" ;;
        *) die "Dependencia de sistema no permitida: $dep" ;;
      esac
      ;;
    dnf|yum)
      case "$dep" in
        cairo) echo "cairo-devel" ;;
        cmake) echo "cmake" ;;
        curl) echo "libcurl-devel" ;;
        fontconfig) echo "fontconfig-devel" ;;
        freetype) echo "freetype-devel" ;;
        fribidi) echo "fribidi-devel" ;;
        gdal) echo "gdal-devel" ;;
        geos) echo "geos-devel" ;;
        git) echo "git" ;;
        harfbuzz) echo "harfbuzz-devel" ;;
        jpeg) echo "libjpeg-turbo-devel" ;;
        libgit2) echo "libgit2-devel" ;;
        libuv) echo "libuv-devel" ;;
        glu) echo "mesa-libGLU-devel" ;;
        make) echo "make" ;;
        opengl) echo "mesa-libGL-devel" ;;
        openssl) echo "openssl-devel" ;;
        pango) echo "pango-devel" ;;
        png) echo "libpng-devel" ;;
        proj) echo "proj-devel" ;;
        sqlite) echo "sqlite-devel" ;;
        tiff) echo "libtiff-devel" ;;
        udunits2) echo "udunits2-devel" ;;
        v8) echo "v8-devel" ;;
        xml2) echo "libxml2-devel" ;;
        x11) echo "libX11-devel libXext-devel libXrender-devel libXt-devel" ;;
        *) die "Dependencia de sistema no permitida: $dep" ;;
      esac
      ;;
    zypper)
      case "$dep" in
        cairo) echo "cairo-devel" ;;
        cmake) echo "cmake" ;;
        curl) echo "libcurl-devel" ;;
        fontconfig) echo "fontconfig-devel" ;;
        freetype) echo "freetype-devel" ;;
        fribidi) echo "fribidi-devel" ;;
        gdal) echo "gdal-devel" ;;
        geos) echo "geos-devel" ;;
        git) echo "git" ;;
        harfbuzz) echo "harfbuzz-devel" ;;
        jpeg) echo "libjpeg-devel" ;;
        libgit2) echo "libgit2-devel" ;;
        libuv) echo "libuv-devel" ;;
        glu) echo "Mesa-libGLU-devel" ;;
        make) echo "make" ;;
        opengl) echo "Mesa-libGL-devel" ;;
        openssl) echo "libopenssl-devel" ;;
        pango) echo "pango-devel" ;;
        png) echo "libpng16-devel" ;;
        proj) echo "proj-devel" ;;
        sqlite) echo "sqlite3-devel" ;;
        tiff) echo "libtiff-devel" ;;
        udunits2) echo "udunits2-devel" ;;
        v8) echo "libv8-devel" ;;
        xml2) echo "libxml2-devel" ;;
        x11) echo "libX11-devel libXext-devel libXrender-devel libXt-devel" ;;
        *) die "Dependencia de sistema no permitida: $dep" ;;
      esac
      ;;
    generic)
      die "No hay gestor de paquetes compatible para instalar dependencias del sistema."
      ;;
  esac
}

install_system_deps() {
  local dep mapped pkg
  local packages=()
  [[ $# -gt 0 ]] || die "Uso: install-system-deps DEPENDENCIA [...]"
  detect_package_manager
  for dep in "$@"; do
    [[ "$dep" =~ ^[a-z0-9][a-z0-9_-]{0,40}$ ]] || die "Dependencia de sistema no valida: $dep"
    mapped="$(system_dep_packages "$dep")"
    for pkg in $mapped; do
      packages+=("$pkg")
    done
  done
  pkg_update
  pkg_install "${packages[@]}"
  echo "Dependencias de sistema instaladas: ${packages[*]}"
}

require_commands() {
  local missing=()
  local cmd
  for cmd in "$@"; do
    if ! need_cmd "$cmd"; then
      missing+=("$cmd")
    fi
  done
  if [[ "${#missing[@]}" -gt 0 ]]; then
    die "Faltan comandos obligatorios: ${missing[*]}. Instalalos con el gestor de paquetes de tu distribucion y vuelve a intentarlo."
  fi
}

install_r_build_dependencies() {
  detect_package_manager
  pkg_update
  case "$PKG_MANAGER" in
    apt)
      pkg_install build-essential gfortran curl ca-certificates make xz-utils tar gzip bzip2 sed coreutils gawk grep findutils diffutils binutils perl \
        libcairo2-dev libpango1.0-dev libjpeg-dev libpng-dev libtiff-dev libuv1-dev
      pkg_install_optional \
        libreadline-dev libbz2-dev liblzma-dev libpcre2-dev zlib1g-dev \
        libcurl4-openssl-dev libssl-dev libxml2-dev libicu-dev \
        libfontconfig1-dev libfreetype6-dev libharfbuzz-dev libfribidi-dev \
        libgl1-mesa-dev libglu1-mesa-dev mesa-common-dev libx11-dev libxext-dev libxrender-dev libxt-dev
      ;;
    dnf|yum)
      pkg_install gcc gcc-c++ gcc-gfortran curl ca-certificates make tar gzip bzip2 xz sed coreutils gawk grep findutils diffutils binutils glibc-devel perl \
        cairo-devel pango-devel libjpeg-turbo-devel libpng-devel libtiff-devel libuv-devel
      pkg_install_optional \
        readline-devel bzip2-devel xz-devel pcre2-devel zlib-devel \
        libcurl-devel openssl-devel libxml2-devel libicu-devel \
        fontconfig-devel freetype-devel harfbuzz-devel fribidi-devel \
        mesa-libGL-devel mesa-libGLU-devel libX11-devel libXext-devel libXrender-devel libXt-devel
      ;;
    zypper)
      pkg_install gcc gcc-c++ gcc-fortran curl ca-certificates make tar gzip bzip2 xz sed coreutils gawk grep findutils diffutils binutils glibc-devel perl \
        cairo-devel pango-devel libjpeg-devel libpng16-devel libtiff-devel libuv-devel
      pkg_install_optional \
        readline-devel libbz2-devel xz-devel pcre2-devel zlib-devel \
        libcurl-devel libopenssl-devel libxml2-devel libicu-devel \
        fontconfig-devel freetype-devel harfbuzz-devel fribidi-devel \
        Mesa-libGL-devel Mesa-libGLU-devel libX11-devel libXext-devel libXrender-devel libXt-devel
      ;;
    generic)
      warn "Modo generico: no se instalaran dependencias de compilacion R automaticamente."
      ;;
  esac
  require_commands gcc g++ gfortran make curl tar gzip sed sort awk grep cut head nproc
}

r_fortran_compat_flags() {
  local tmpdir flag
  tmpdir="$(mktemp -d /tmp/solar-fortran-check.XXXXXX)"
  printf '      subroutine solarcheck(x)\n      integer x\n      end\n' > "$tmpdir/check.f"
  flag="-fallow-argument-mismatch"
  if gfortran -c "$flag" "$tmpdir/check.f" -o "$tmpdir/check.o" >/dev/null 2>&1; then
    rm -rf "$tmpdir"
    printf '%s' "$flag"
    return 0
  fi
  rm -rf "$tmpdir"
  printf ''
}

find_python_runtime_binary() {
  local version="$1"
  local candidate
  for candidate in \
    "python$version" \
    "/usr/bin/python$version" \
    "/usr/local/bin/python$version" \
    "/opt/python/$version/bin/python$version" \
    "$PYTHON_RUNTIME_ROOT/$version/bin/python$version" \
    "/opt/solar-workbench/python/$version/bin/python$version"; do
    if [[ "$candidate" == */* ]]; then
      [[ -x "$candidate" ]] && { echo "$candidate"; return 0; }
    elif command -v "$candidate" >/dev/null 2>&1; then
      command -v "$candidate"
      return 0
    fi
  done
  return 1
}

install_python_build_dependencies() {
  detect_package_manager
  pkg_update
  case "$PKG_MANAGER" in
    apt)
      pkg_install build-essential curl ca-certificates make tar xz-utils pkg-config sed coreutils grep
      pkg_install_optional \
        libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
        libffi-dev liblzma-dev tk-dev uuid-dev libncursesw5-dev
      ;;
    dnf|yum)
      pkg_install gcc gcc-c++ curl ca-certificates make tar xz bzip2 pkgconf-pkg-config sed coreutils gawk grep
      pkg_install_optional \
        openssl-devel zlib-devel bzip2-devel readline-devel sqlite-devel \
        libffi-devel xz-devel tk-devel libuuid-devel ncurses-devel
      ;;
    zypper)
      pkg_install gcc gcc-c++ curl ca-certificates make tar xz bzip2 pkg-config sed coreutils gawk grep
      pkg_install_optional \
        libopenssl-devel zlib-devel libbz2-devel readline-devel sqlite3-devel \
        libffi-devel xz-devel tk-devel libuuid-devel ncurses-devel
      ;;
    generic)
      warn "Modo generico: no se instalaran dependencias de compilacion Python automaticamente."
      require_commands gcc make curl tar sed sort grep
      ;;
  esac
}

latest_python_source_version() {
  local minor="$1"
  local listing latest minor_regex
  minor_regex="${minor//./\\.}"
  listing="$(curl -fsSL https://www.python.org/ftp/python/)" || return 1
  latest="$(printf '%s\n' "$listing" \
    | grep -Eo "href=\"${minor_regex}\\.[0-9]+/" \
    | sed -E 's/^href="//; s#/##' \
    | sort -V \
    | tail -n 1)"
  [[ -n "$latest" ]] || return 1
  printf '%s' "$latest"
}

build_python_runtime_from_source() {
  local version="$1"
  local full_version="${PYTHON_SOURCE_VERSION:-}"
  local prefix="$PYTHON_RUNTIME_ROOT/$version"
  local build_root source_dir archive_url archive_path jobs

  if [[ -x "$prefix/bin/python$version" ]]; then
    echo "$prefix/bin/python$version"
    return 0
  fi

  install_python_build_dependencies
  if [[ -z "$full_version" ]]; then
    full_version="$(latest_python_source_version "$version")" || die "No se pudo localizar la ultima version fuente de Python $version en python.org."
  fi
  [[ "$full_version" == "$version".* ]] || die "La version fuente '$full_version' no corresponde con Python $version."

  build_root="$(mktemp -d "/tmp/solar-python-$version.XXXXXX")"
  source_dir="$build_root/Python-$full_version"
  archive_url="https://www.python.org/ftp/python/$full_version/Python-$full_version.tgz"
  archive_path="$build_root/Python-$full_version.tgz"

  mkdir -p "$prefix"
  echo "Descargando Python $full_version desde $archive_url"
  if ! curl -fL "$archive_url" -o "$archive_path"; then
    archive_url="https://www.python.org/ftp/python/$full_version/Python-$full_version.tar.xz"
    archive_path="$build_root/Python-$full_version.tar.xz"
    echo "Reintentando descarga desde $archive_url"
    curl -fL "$archive_url" -o "$archive_path"
  fi
  case "$archive_path" in
    *.tar.xz) tar -xJf "$archive_path" -C "$build_root" ;;
    *) tar -xzf "$archive_path" -C "$build_root" ;;
  esac

  cd "$source_dir"
  echo "Configurando Python $full_version en $prefix"
  ./configure --prefix="$prefix" --with-ensurepip=install
  jobs="$(nproc 2>/dev/null || echo 2)"
  echo "Compilando Python $full_version con $jobs procesos"
  make -j"$jobs"
  make altinstall

  "$prefix/bin/python$version" -m venv --help >/dev/null 2>&1 || die "Python $version se compilo, pero el modulo venv no quedo disponible."
  "$prefix/bin/python$version" - <<'PY' || die "Python se compilo, pero faltan modulos base necesarios: ssl, zlib, sqlite3 o venv."
import ssl
import sqlite3
import venv
import zlib
PY
  "$prefix/bin/python$version" -m ensurepip --upgrade >/dev/null 2>&1 || true
  "$prefix/bin/python$version" -m pip install --upgrade pip setuptools wheel >/dev/null 2>&1 || warn "Python $version instalado, pero no se pudo actualizar pip/setuptools/wheel."
  rm -rf "$build_root"
  echo "$prefix/bin/python$version"
}

install_python_runtime() {
  local version="$1"
  local runtime_root="${2:-${DEVRA_PYTHON_RUNTIME_ROOT:-/opt/solar-workbench/python}}"
  valid_python_runtime_version "$version" || die "Version Python no valida. Versiones permitidas: 3.11, 3.12, 3.13."
  valid_python_runtime_root "$runtime_root" || die "Ruta de runtimes Python no permitida"
  PYTHON_RUNTIME_ROOT="$runtime_root"
  local binary
  if binary="$(find_python_runtime_binary "$version")"; then
    if "$binary" -m venv --help >/dev/null 2>&1; then
      echo "$binary"
      return 0
    fi
    warn "Python $version existe pero el modulo venv no esta disponible. Se intentara instalar el soporte venv o compilar Python completo."
  fi

  detect_package_manager
  pkg_update
  case "$PKG_MANAGER" in
    apt)
      if ! pkg_install "python$version" "python$version-venv" "python$version-dev"; then
        warn "No se pudo instalar Python $version desde apt. Se intentara compilar desde fuente oficial."
        build_python_runtime_from_source "$version"
        return 0
      fi
      ;;
    dnf|yum)
      if ! pkg_install "python$version" "python$version-devel"; then
        if ! pkg_install "python$version"; then
          warn "No se pudo instalar Python $version desde $PKG_MANAGER. Se intentara compilar desde fuente oficial."
          build_python_runtime_from_source "$version"
          return 0
        fi
      fi
      ;;
    zypper)
      local nodot="${version/./}"
      if ! pkg_install "python$nodot" "python$nodot-devel"; then
        if ! pkg_install "python$version"; then
          warn "No se pudo instalar Python $version desde zypper. Se intentara compilar desde fuente oficial."
          build_python_runtime_from_source "$version"
          return 0
        fi
      fi
      ;;
    generic)
      warn "Modo generico: se intentara compilar Python $version desde fuente oficial."
      build_python_runtime_from_source "$version"
      return 0
      ;;
  esac

  if ! binary="$(find_python_runtime_binary "$version")"; then
    die "La instalacion finalizo, pero no se encontro el binario python$version."
  fi
  "$binary" -m venv --help >/dev/null 2>&1 || die "Python $version se instalo, pero falta el modulo venv."
  echo "$binary"
}

ensure_legacy_pcre() {
  local dep="/opt/devra/build-deps/pcre-8.45"
  if [[ -f "$dep/lib/libpcre.so" ]]; then
    echo "$dep"
    return
  fi
  local build_root="/tmp/devra-pcre-build"
  rm -rf "$build_root"
  mkdir -p "$build_root" /opt/devra/build-deps
  cd "$build_root"
  curl -fsSL "https://downloads.sourceforge.net/project/pcre/pcre/8.45/pcre-8.45.tar.bz2" -o pcre-8.45.tar.bz2
  tar -xjf pcre-8.45.tar.bz2
  cd pcre-8.45
  ./configure --prefix="$dep" --enable-utf --enable-unicode-properties >&2
  make -j"$(nproc)" >&2
  make install >&2
  echo "$dep"
}

ensure_legacy_curl() {
  local dep="/opt/devra/build-deps/curl-7.88.1"
  if [[ -f "$dep/bin/curl-config" ]]; then
    echo "$dep"
    return
  fi
  local build_root="/tmp/devra-curl-build"
  rm -rf "$build_root"
  mkdir -p "$build_root" /opt/devra/build-deps
  cd "$build_root"
  curl -fsSL "https://curl.se/download/curl-7.88.1.tar.xz" -o curl-7.88.1.tar.xz
  tar -xJf curl-7.88.1.tar.xz
  cd curl-7.88.1
  ./configure \
    --prefix="$dep" \
    --with-openssl \
    --disable-static \
    --without-libidn2 \
    --without-libpsl \
    --without-nghttp2 \
    --without-brotli \
    --without-zstd \
    --disable-ldap \
    --disable-ldaps \
    --disable-manual >&2
  make -j"$(nproc)" >&2
  make install >&2
  echo "$dep"
}

create_user() {
  local username="$1"
  local quota_bytes="$2"
  local workspace_root="$3"
  local image_root="$4"
  local app_user="$5"
  valid_user "$username" || die "Nombre de usuario no válido"
  [[ "$quota_bytes" =~ ^[0-9]+$ ]] || die "Cuota no válida"
  [[ "$quota_bytes" -ge 134217728 ]] || die "Cuota mínima: 128 MB"

  mkdir -p "$workspace_root" "$image_root"
  chmod 0710 "$workspace_root" 2>/dev/null || true
  local image="$image_root/$username.img"
  local mountpoint="$workspace_root/$username"
  mkdir -p "$mountpoint"

  if ! grep -qs " $mountpoint " /proc/mounts; then
    if [[ ! -f "$image" ]]; then
      truncate -s "$quota_bytes" "$image"
      mkfs.ext4 -q -F "$image"
    fi
    mount -o loop,nosuid,nodev "$image" "$mountpoint"
  fi
  chown "$app_user:$app_user" "$mountpoint"
  chmod 700 "$mountpoint"

  local fstab_line="$image $mountpoint ext4 loop,nosuid,nodev 0 0"
  if ! grep -Fqs "$image $mountpoint " /etc/fstab; then
    printf '%s\n' "$fstab_line" >> /etc/fstab
  fi
  echo "$mountpoint"
}

ensure_session_user() {
  local username="$1"
  local home="$2"
  local group="$3"
  valid_user "$username" || die "Nombre de usuario no valido"
  [[ "$home" == /* ]] || die "Home no valida"
  getent group "$group" >/dev/null || groupadd --system "$group"
  ensure_home_parent_access "$home" "$group"
  if ! id -u "$username" >/dev/null 2>&1; then
    local shell
    local uid
    shell="$(command -v nologin || echo /usr/sbin/nologin)"
    uid=$((50000 + ($(printf '%s' "$username" | cksum | awk '{print $1}') % 10000)))
    if getent passwd "$uid" >/dev/null; then
      useradd --system --home-dir "$home" --shell "$shell" --gid "$group" "$username"
    else
      useradd --system --uid "$uid" --home-dir "$home" --shell "$shell" --gid "$group" "$username"
    fi
  else
    usermod -d "$home" -a -G "$group" "$username" >/dev/null 2>&1 || true
  fi
  mkdir -p "$home"
  chown -R "$username:$group" "$home"
  chmod -R o-rwx "$home"
  mkdir -p "$home/R/library" "$home/.devra/jobs"
  chown -R "$username:$group" "$home/R" "$home/.devra"
  find "$home" -type d -exec chmod 2770 {} +
  find "$home" -type f -exec chmod g+rw {} +
  echo "$username"
}

prepare_session_home() {
  local username="$1"
  local home="$2"
  local group="$3"
  local profile_b64="${4:-}"
  ensure_session_user "$username" "$home" "$group" >/dev/null
  if [[ -n "$profile_b64" ]]; then
    printf '%s' "$profile_b64" | base64 -d > "$home/.devra/Rprofile"
    chown "$username:$group" "$home/.devra/Rprofile"
    chmod 660 "$home/.devra/Rprofile"
  fi
  echo "$home/.devra/Rprofile"
}

apply_project_acl() {
  local root="$1"
  local owner="$2"
  local group="$3"
  shift 3
  mkdir -p "$root"
  if id -u "$owner" >/dev/null 2>&1; then
    chown -R "$owner:$group" "$root" 2>/dev/null || true
  else
    chgrp -R "$group" "$root" 2>/dev/null || true
  fi
  find "$root" -type d -exec chmod 2700 {} + 2>/dev/null || true
  find "$root" -type f -exec chmod 0600 {} + 2>/dev/null || true
  if command -v setfacl >/dev/null 2>&1; then
    setfacl -Rb "$root" 2>/dev/null || true
    setfacl -R -m "u:${owner}:rwx" "$root" 2>/dev/null || true
    find "$root" -type d -exec setfacl -m "d:u:${owner}:rwx" {} + 2>/dev/null || true
    local username
    for username in "$@"; do
      id -u "$username" >/dev/null 2>&1 || continue
      setfacl -R -m "u:${username}:rwx" "$root" 2>/dev/null || true
      find "$root" -type d -exec setfacl -m "d:u:${username}:rwx" {} + 2>/dev/null || true
    done
  else
    echo "AVISO: setfacl no esta disponible; se aplican permisos de grupo como compatibilidad." >&2
    find "$root" -type d -exec chmod 2770 {} + 2>/dev/null || true
    find "$root" -type f -exec chmod 0660 {} + 2>/dev/null || true
  fi
  echo "Permisos de proyecto actualizados en $root"
}

write_session_file() {
  local username="$1"
  local home="$2"
  local group="$3"
  local relative="$4"
  local content_b64="$5"
  local target
  ensure_session_user "$username" "$home" "$group" >/dev/null
  target="$(session_target_path "$home" "$relative")"
  mkdir -p "$(dirname "$target")"
  printf '%s' "$content_b64" | base64 -d > "$target"
  chown "$username:$group" "$target"
  chmod 660 "$target"
  echo "$target"
}

repair_python_env() {
  local root="$1"
  local owner="$2"
  local group="$3"
  valid_python_env_root "$root" || die "Ruta de environment Python no permitida"
  id -u "$owner" >/dev/null 2>&1 || die "Usuario propietario no valido"
  getent group "$group" >/dev/null || die "Grupo propietario no valido"
  [[ -e "$root" ]] || return 0
  chown -R "$owner:$group" "$root" 2>/dev/null || true
  find "$root" -type d -exec chmod 0755 {} + 2>/dev/null || true
  find "$root" -type f -exec chmod 0644 {} + 2>/dev/null || true
  if [[ -d "$root/bin" ]]; then
    find "$root/bin" -type f -exec chmod 0755 {} + 2>/dev/null || true
  fi
  find "$root" -type f \( -name '*.so' -o -name '*.so.*' \) -exec chmod 0755 {} + 2>/dev/null || true
  echo "Permisos Python verificados en $root"
}

run_session() {
  local username="$1"
  local memory_mb="$2"
  local max_processes="$3"
  local cpu_percent="$4"
  shift 4
  [[ "${1:-}" == "--" ]] || die "Uso: run-session USER MEMORY_MB MAX_PROCESSES CPU_PERCENT -- COMMAND"
  shift
  valid_user "$username" || die "Nombre de usuario no valido"
  id -u "$username" >/dev/null 2>&1 || die "El usuario Linux no existe"
  [[ "$memory_mb" =~ ^[0-9]+$ ]] || die "Limite de memoria no valido"
  [[ "$max_processes" =~ ^[0-9]+$ ]] || die "Limite de procesos no valido"
  [[ "$cpu_percent" =~ ^[0-9]+$ ]] || die "Limite CPU no valido"
  [[ $# -ge 1 ]] || die "Comando requerido"
  local home_dir
  home_dir="$(getent passwd "$username" | cut -d: -f6)"
  [[ -n "$home_dir" && -d "$home_dir" ]] || die "Home del usuario no encontrada"
  cd "$home_dir"
  umask 0007
  if command -v systemd-run >/dev/null 2>&1; then
    local -a props
    props=(--quiet --scope --collect --uid "$username" -p MemorySwapMax=0 -p UMask=0007)
    if systemd-run --help 2>/dev/null | grep -q -- "--working-directory"; then
      props+=(--working-directory="$home_dir")
    fi
    [[ "$memory_mb" -gt 0 ]] && props+=(-p "MemoryMax=${memory_mb}M")
    [[ "$max_processes" -gt 0 ]] && props+=(-p "TasksMax=$max_processes")
    [[ "$cpu_percent" -gt 0 ]] && props+=(-p "CPUQuota=${cpu_percent}%")
    if systemd-run "${props[@]}" -- /bin/true >/dev/null 2>&1; then
      exec systemd-run "${props[@]}" -- "$@"
    fi
  fi
  if [[ "$memory_mb" -gt 0 ]]; then
    ulimit -v $((memory_mb * 1024))
  fi
  if [[ "$max_processes" -gt 0 ]]; then
    ulimit -u "$max_processes"
  fi
  exec runuser -u "$username" -- env HOME="$home_dir" USER="$username" LOGNAME="$username" "$@"
}

delete_user() {
  local username="$1"
  local workspace_root="$2"
  local image_root="$3"
  local destroy="${4:-}"
  valid_user "$username" || die "Nombre de usuario no válido"
  local image="$image_root/$username.img"
  local mountpoint="$workspace_root/$username"
  if grep -qs " $mountpoint " /proc/mounts; then
    umount "$mountpoint"
  fi
  sed -i "\|$image $mountpoint |d" /etc/fstab
  if [[ "$destroy" == "--destroy" ]]; then
    rm -rf -- "$mountpoint" "$image"
  fi
  if id -u "$username" >/dev/null 2>&1; then
    if command -v pkill >/dev/null 2>&1; then
      pkill -TERM -u "$username" >/dev/null 2>&1 || true
      sleep 1
      pkill -KILL -u "$username" >/dev/null 2>&1 || true
    fi
    userdel "$username" >/dev/null 2>&1 || true
  fi
}

resize_user() {
  local username="$1"
  local quota_bytes="$2"
  local workspace_root="$3"
  local image_root="$4"
  valid_user "$username" || die "Nombre de usuario no válido"
  [[ "$quota_bytes" =~ ^[0-9]+$ ]] || die "Cuota no válida"
  local image="$image_root/$username.img"
  local mountpoint="$workspace_root/$username"
  [[ -f "$image" ]] || die "No existe imagen de cuota para el usuario"
  local current
  current="$(stat -c '%s' "$image")"
  if [[ "$quota_bytes" -lt "$current" ]]; then
    echo "La cuota lógica se reducirá en la aplicación; la imagen no se encoge en caliente."
    exit 0
  fi
  truncate -s "$quota_bytes" "$image"
  if grep -qs " $mountpoint " /proc/mounts; then
    local source
    source="$(findmnt -no SOURCE "$mountpoint")"
    resize2fs "$source" >/dev/null
  else
    e2fsck -fy "$image" >/dev/null
    resize2fs "$image" >/dev/null
  fi
}

install_r() {
  local version="$1"
  local r_root="${2:-/opt/R}"
  valid_version "$version" || die "Versión R no válida"
  [[ "$r_root" == /* ]] || die "Ruta raiz de R no valida"
  local major="${version%%.*}"
  local prefix="$r_root/$version"
  local build_root="/tmp/devra-r-build"
  local tarball="$build_root/R-$version.tar.gz"

  if [[ -x "$prefix/bin/R" ]]; then
    echo "$prefix/bin/R"
    exit 0
  fi

  install_r_build_dependencies

  rm -rf "$build_root"
  mkdir -p "$build_root" "$r_root"
  curl -fsSL "https://cran.r-project.org/src/base/R-$major/R-$version.tar.gz" -o "$tarball"
  tar -xzf "$tarball" -C "$build_root"
  local minor curl_dep
  minor="$(printf '%s' "$version" | cut -d. -f2)"
  curl_dep="$(ensure_legacy_curl)"
  cd "$build_root/R-$version"
  local fortran_flags
  fortran_flags="$(r_fortran_compat_flags)"

  if [[ "$major" -lt 4 || ( "$major" -eq 4 && "$minor" -lt 2 ) ]]; then
    local pcre_dep
    pcre_dep="$(ensure_legacy_pcre)"
    env \
      PATH="$curl_dep/bin:$PATH" \
      F77="gfortran" \
      FC="gfortran" \
      CPPFLAGS="-I$pcre_dep/include -I$curl_dep/include" \
      LDFLAGS="-L$pcre_dep/lib -L$curl_dep/lib -Wl,-rpath,$pcre_dep/lib -Wl,-rpath,$curl_dep/lib" \
      LD_LIBRARY_PATH="$pcre_dep/lib:$curl_dep/lib" \
      CFLAGS="-std=gnu99 -fcommon -Wno-error=implicit-function-declaration -Wno-error=incompatible-pointer-types" \
      CXXFLAGS="-fcommon -Wno-error=implicit-function-declaration -Wno-error=incompatible-pointer-types" \
      FFLAGS="$fortran_flags" \
      FCFLAGS="$fortran_flags" \
      ./configure --prefix="$prefix" --enable-R-shlib --with-x=no
  else
    env \
      PATH="$curl_dep/bin:$PATH" \
      F77="gfortran" \
      FC="gfortran" \
      CPPFLAGS="-I$curl_dep/include" \
      LDFLAGS="-L$curl_dep/lib -Wl,-rpath,$curl_dep/lib" \
      LD_LIBRARY_PATH="$curl_dep/lib" \
      CFLAGS="-std=gnu17 -fcommon -Wno-error=implicit-function-declaration -Wno-error=incompatible-pointer-types" \
      CXXFLAGS="-fcommon -Wno-error=implicit-function-declaration -Wno-error=incompatible-pointer-types" \
      FFLAGS="$fortran_flags" \
      FCFLAGS="$fortran_flags" \
      ./configure --prefix="$prefix" --enable-R-shlib --with-x=no
  fi
  make -j"$(nproc)"
  make install
  "$prefix/bin/R" --slave --no-save --no-restore -e 'if (!isTRUE(capabilities("cairo"))) stop("R no tiene soporte cairo para graficos headless", call. = FALSE); f <- tempfile(fileext = ".png"); grDevices::png(filename = f); plot(1:3); grDevices::dev.off(); if (!file.exists(f) || isTRUE(file.info(f)$size <= 0)) stop("R no pudo generar PNG headless", call. = FALSE)' \
    || die "R $version se instalo, pero no puede generar graficos PNG sin X11. Instala cairo/pango/libpng/libjpeg/libtiff de desarrollo y reinstala esta version R."
  "$prefix/bin/R" --version | head -1
}

cluster_sync() {
  local script_path="$1"
  local command="${2:-push}"
  [[ "$script_path" == /* ]] || die "Ruta de sincronizacion no valida"
  [[ "$(basename "$script_path")" == "solar-cluster-sync" ]] || die "Script de sincronizacion no permitido"
  [[ -x "$script_path" ]] || die "Script de sincronizacion no encontrado o no ejecutable"
  valid_sync_command "$command" || die "Comando de sincronizacion no valido"
  case "$command" in
    push|bidirectional)
      [[ $# -eq 2 ]] || die "Uso: cluster-sync SCRIPT [push|bidirectional]"
      "$script_path" "$command"
      ;;
    delete-user)
      [[ $# -eq 3 ]] || die "Uso: cluster-sync SCRIPT delete-user USER"
      valid_user "$3" || die "Nombre de usuario no valido"
      "$script_path" "$command" "$3"
      ;;
    delete-package)
      [[ $# -eq 4 ]] || die "Uso: cluster-sync SCRIPT delete-package R_MINOR PACKAGE"
      valid_minor_version "$3" || die "Version menor R no valida"
      valid_package "$4" || die "Nombre de paquete no valido"
      "$script_path" "$command" "$3" "$4"
      ;;
    delete-python-package)
      [[ $# -eq 4 ]] || die "Uso: cluster-sync SCRIPT delete-python-package PY_MINOR PACKAGE"
      valid_minor_version "$3" || die "Version menor Python no valida"
      valid_python_package "$4" || die "Nombre de paquete Python no valido"
      "$script_path" "$command" "$3" "$4"
      ;;
    delete-home-path)
      [[ $# -eq 5 ]] || die "Uso: cluster-sync SCRIPT delete-home-path USER RELATIVE_PATH"
      valid_user "$3" || die "Nombre de usuario no valido"
      valid_home_relative "$4" || die "Ruta relativa no valida"
      "$script_path" "$command" "$3" "$4"
      ;;
    delete-workspace-path)
      [[ $# -eq 4 ]] || die "Uso: cluster-sync SCRIPT delete-workspace-path RELATIVE_PATH"
      valid_home_relative "$3" || die "Ruta relativa no valida"
      "$script_path" "$command" "$3"
      ;;
    set-nodes)
      [[ $# -eq 3 ]] || die "Uso: cluster-sync SCRIPT set-nodes NODE1,NODE2"
      valid_cluster_nodes "$3" || die "Lista de nodos no valida"
      "$script_path" "$command" "$3"
      ;;
  esac
}

cluster_nodes() {
  local env_file="$1"
  local nodes="$2"
  [[ "$env_file" == /etc/* ]] || die "Fichero de configuracion no permitido"
  [[ -f "$env_file" ]] || die "Fichero de configuracion no encontrado"
  valid_cluster_nodes "$nodes" || die "Lista de nodos no valida"

  local tmp owner mode
  tmp="$(mktemp)"
  owner="$(stat -c '%u:%g' "$env_file")"
  mode="$(stat -c '%a' "$env_file")"
  grep -v '^DEVRA_CLUSTER_NODES=' "$env_file" > "$tmp" || true
  nodes="${nodes//\\,/,}"
  nodes="${nodes//\\/}"
  printf 'DEVRA_CLUSTER_NODES=%s\n' "$nodes" >> "$tmp"
  chown "$owner" "$tmp"
  chmod "$mode" "$tmp"
  mv "$tmp" "$env_file"
  echo "Nodos del cluster actualizados: $nodes"
}

write_config() {
  local env_file="$1"
  local encoded="$2"
  valid_config_file "$env_file" || die "Fichero de configuracion no permitido"
  [[ ${#encoded} -le 180000 ]] || die "Configuracion demasiado grande"
  local tmp owner mode group_name
  tmp="$(mktemp)"
  if [[ -f "$env_file" ]]; then
    owner="$(stat -c '%u:%g' "$env_file")"
    mode="$(stat -c '%a' "$env_file")"
  else
    owner="root:root"
    mode="0640"
  fi
  printf '%s' "$encoded" | base64 -d > "$tmp"
  group_name="${owner#*:}"
  chown "$owner" "$tmp" 2>/dev/null || chown root:root "$tmp"
  chmod "$mode" "$tmp" 2>/dev/null || chmod 0640 "$tmp"
  mv "$tmp" "$env_file"
  echo "Configuracion guardada en $env_file"
}

case "${1:-}" in
  create-user)
    [[ $# -eq 6 ]] || die "Uso: create-user USER QUOTA_BYTES WORKSPACE_ROOT IMAGE_ROOT APP_USER"
    create_user "$2" "$3" "$4" "$5" "$6"
    ;;
  delete-user)
    [[ $# -ge 4 ]] || die "Uso: delete-user USER WORKSPACE_ROOT IMAGE_ROOT [--destroy]"
    delete_user "$2" "$3" "$4" "${5:-}"
    ;;
  resize-user)
    [[ $# -eq 5 ]] || die "Uso: resize-user USER QUOTA_BYTES WORKSPACE_ROOT IMAGE_ROOT"
    resize_user "$2" "$3" "$4" "$5"
    ;;
  install-r)
    [[ $# -eq 2 || $# -eq 3 ]] || die "Uso: install-r VERSION [R_ROOT]"
    install_r "$2" "${3:-/opt/R}"
    ;;
  install-python)
    [[ $# -eq 2 || $# -eq 3 ]] || die "Uso: install-python VERSION [PYTHON_RUNTIME_ROOT]"
    install_python_runtime "$2" "${3:-}"
    ;;
  install-system-deps)
    [[ $# -ge 2 ]] || die "Uso: install-system-deps DEPENDENCIA [...]"
    install_system_deps "${@:2}"
    ;;
  ensure-session-user)
    [[ $# -eq 4 ]] || die "Uso: ensure-session-user USER HOME GROUP"
    ensure_session_user "$2" "$3" "$4"
    ;;
  prepare-session-home)
    [[ $# -eq 5 ]] || die "Uso: prepare-session-home USER HOME GROUP PROFILE_B64"
    prepare_session_home "$2" "$3" "$4" "$5"
    ;;
  project-acl)
    [[ $# -ge 4 ]] || die "Uso: project-acl PROJECT_PATH APP_USER GROUP [USER ...]"
    apply_project_acl "${@:2}"
    ;;
  write-session-file)
    [[ $# -eq 6 ]] || die "Uso: write-session-file USER HOME GROUP RELATIVE_PATH CONTENT_B64"
    write_session_file "$2" "$3" "$4" "$5" "$6"
    ;;
  run-session)
    [[ $# -ge 7 ]] || die "Uso: run-session USER MEMORY_MB MAX_PROCESSES CPU_PERCENT -- COMMAND"
    run_session "${@:2}"
    ;;
  cluster-sync)
    [[ $# -ge 2 ]] || die "Uso: cluster-sync SCRIPT [push|bidirectional|delete-user|delete-package|delete-python-package|delete-home-path|delete-workspace-path] [...]"
    cluster_sync "${@:2}"
    ;;
  cluster-nodes)
    [[ $# -eq 3 ]] || die "Uso: cluster-nodes ENV_FILE NODE1,NODE2"
    cluster_nodes "$2" "$3"
    ;;
  write-config)
    [[ $# -eq 3 ]] || die "Uso: write-config ENV_FILE BASE64_CONTENT"
    write_config "$2" "$3"
    ;;
  repair-python-env)
    [[ $# -eq 4 ]] || die "Uso: repair-python-env PATH OWNER GROUP"
    repair_python_env "$2" "$3" "$4"
    ;;
  *)
    die "Comando no soportado"
    ;;
esac
