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

LAB_ROOT="${SORIA_LINUX_PROCESS_LAB:-/tmp/soria-linux-processes-${USER:-$(id -un)}}"
MARKER="$LAB_ROOT/.soria-linux-processes-lab"
PID_FILE="$LAB_ROOT/run/parent.pid"
PGID_FILE="$LAB_ROOT/run/process-group.pgid"

usage() {
  cat <<'USAGE'
Usage: module3-processes-software-lab.sh {setup|start|status|signal SIGNAL|stop|reset}

Signals accepted by the signal command: HUP, USR1, STOP, CONT, TERM.
USAGE
}

is_safe_root() {
  case "$LAB_ROOT" in
    /tmp/soria-linux-processes-*|"$HOME"/soria-linux-processes-*) return 0 ;;
    *) return 1 ;;
  esac
}

require_safe_root() {
  is_safe_root || { printf 'Refusing unsafe lab path: %s\n' "$LAB_ROOT" >&2; exit 2; }
}

is_running() {
  [[ -f "$PID_FILE" ]] || return 1
  local pid
  pid=$(cat "$PID_FILE")
  [[ "$pid" =~ ^[0-9]+$ ]] || return 1
  kill -0 "$pid" 2>/dev/null
}

read_isolated_ids() {
  [[ -f "$PID_FILE" && -f "$PGID_FILE" ]] || {
    printf 'Missing PID or PGID state file\n' >&2
    return 1
  }
  LAB_PID=$(cat "$PID_FILE")
  LAB_PGID=$(cat "$PGID_FILE")
  [[ "$LAB_PID" =~ ^[0-9]+$ && "$LAB_PGID" =~ ^[0-9]+$ ]] || {
    printf 'Invalid PID or PGID state\n' >&2
    return 1
  }
  [[ "$LAB_PID" == "$LAB_PGID" ]] || {
    printf 'Refusing non-isolated process group: pid=%s pgid=%s\n' "$LAB_PID" "$LAB_PGID" >&2
    return 1
  }
}

setup_lab() {
  require_safe_root
  if [[ -e "$LAB_ROOT" && ! -f "$MARKER" ]]; then
    printf 'Refusing existing unmarked directory: %s\n' "$LAB_ROOT" >&2
    exit 2
  fi

  mkdir -p "$LAB_ROOT"/{bin,config,logs,run,reports}
  : > "$MARKER"

  cat > "$LAB_ROOT/bin/process-tree.sh" <<'WORKER'
#!/usr/bin/env bash
set -u
role="${1:-parent}"
log_dir="${2:?log directory required}"
mkdir -p "$log_dir"
log_file="$log_dir/$role.log"
children=()

log() {
  printf '%s role=%s pid=%s ppid=%s pgid=%s sid=%s event=%s\n' \
    "$(date -u +%FT%TZ)" "$role" "$$" "$PPID" \
    "$(ps -o pgid= -p $$ | tr -d ' ')" \
    "$(ps -o sid= -p $$ | tr -d ' ')" "$1" >> "$log_file"
}

forward_and_exit() {
  log 'signal=TERM'
  if ((${#children[@]})); then
    kill -TERM "${children[@]}" 2>/dev/null || true
    wait "${children[@]}" 2>/dev/null || true
  fi
  exit 0
}

trap 'log signal=HUP' HUP
trap 'log signal=USR1' USR1
trap forward_and_exit TERM INT

if [[ "$role" == parent ]]; then
  "$0" child-a "$log_dir" & children+=("$!")
  "$0" child-b "$log_dir" & children+=("$!")
  log "children=${children[*]}"
  while :; do
    sleep 2
    alive=0
    for child in "${children[@]}"; do
      kill -0 "$child" 2>/dev/null && alive=$((alive + 1))
    done
    log "heartbeat children_alive=$alive"
  done
else
  counter=0
  while :; do
    counter=$((counter + 1))
    log "heartbeat counter=$counter"
    sleep 2
  done
fi
WORKER

  cat > "$LAB_ROOT/bin/package-report.sh" <<'PKG'
#!/usr/bin/env bash
set -euo pipefail

if command -v dpkg-query >/dev/null 2>&1; then
  echo 'family=debian'
  dpkg-query -W -f='package=${binary:Package} version=${Version} status=${db:Status-Abbrev}\n' bash coreutils 2>/dev/null || true
  command -v apt-cache >/dev/null 2>&1 && apt-cache policy bash | sed -n '1,8p'
elif command -v rpm >/dev/null 2>&1; then
  echo 'family=rpm'
  rpm -q --qf 'package=%{NAME} version=%{VERSION}-%{RELEASE} arch=%{ARCH}\n' bash coreutils 2>/dev/null || true
  command -v dnf >/dev/null 2>&1 && dnf repolist --enabled 2>/dev/null | sed -n '1,12p'
else
  echo 'family=unknown'
  exit 1
fi
PKG

  cat > "$LAB_ROOT/config/soria-agent.conf" <<'CONF'
# Configuration pédagogique SORIA
agent_name=soria-lab
interval_seconds=10
log_level=info
output_file=/var/log/soria-agent.log
CONF

  cat > "$LAB_ROOT/config/soria-agent.conf.expected" <<'CONF'
# Configuration pédagogique SORIA
agent_name=soria-lab
interval_seconds=30
log_level=warning
output_file=/var/log/soria-agent.log
CONF

  chmod 0755 "$LAB_ROOT/bin/process-tree.sh" "$LAB_ROOT/bin/package-report.sh"
  chmod 0644 "$LAB_ROOT/config/"*.conf*
  printf 'Lab ready: %s\n' "$LAB_ROOT"
}

start_lab() {
  require_safe_root
  command -v setsid >/dev/null 2>&1 || {
    printf 'setsid is required to create an isolated process group\n' >&2
    exit 1
  }
  [[ -f "$MARKER" ]] || setup_lab
  if is_running; then
    printf 'Lab already running with PID %s\n' "$(cat "$PID_FILE")"
    return 0
  fi

  rm -f "$PID_FILE" "$PGID_FILE"
  : > "$LAB_ROOT/logs/launcher.log"

  nohup setsid "$LAB_ROOT/bin/process-tree.sh" parent "$LAB_ROOT/logs" \
    >> "$LAB_ROOT/logs/launcher.log" 2>&1 &
  local pid=$!
  printf '%s\n' "$pid" > "$PID_FILE"

  for _ in 1 2 3 4 5; do
    kill -0 "$pid" 2>/dev/null && break
    sleep 1
  done
  kill -0 "$pid" 2>/dev/null || { printf 'Process tree failed to start\n' >&2; exit 1; }

  local pgid
  pgid=$(ps -o pgid= -p "$pid" | tr -d ' ')
  printf '%s\n' "$pgid" > "$PGID_FILE"
  if ! read_isolated_ids; then
    kill -TERM "$pid" 2>/dev/null || true
    rm -f "$PID_FILE" "$PGID_FILE"
    exit 1
  fi
  printf 'Process tree started: pid=%s pgid=%s\n' "$LAB_PID" "$LAB_PGID"
}

show_status() {
  require_safe_root
  [[ -f "$MARKER" ]] || { printf 'Lab not initialized: %s\n' "$LAB_ROOT" >&2; exit 1; }

  printf 'Lab root: %s\n' "$LAB_ROOT"
  if is_running && read_isolated_ids; then
    printf 'state=running parent_pid=%s pgid=%s\n' "$LAB_PID" "$LAB_PGID"
    ps -o user,pid,ppid,pgid,sid,stat,ni,etime,tty,cmd --forest -g "$LAB_PGID"
  else
    printf 'state=stopped\n'
  fi

  printf '\nRecent events:\n'
  tail -n 4 "$LAB_ROOT"/logs/*.log 2>/dev/null || true
}

signal_lab() {
  require_safe_root
  is_running || { printf 'Process tree is not running\n' >&2; exit 1; }
  read_isolated_ids
  local signal="${1:-}"
  case "$signal" in
    HUP|USR1|STOP|CONT|TERM) ;;
    *) printf 'Unsupported signal: %s\n' "$signal" >&2; exit 2 ;;
  esac
  kill -s "$signal" -- "-$LAB_PGID"
  printf 'Sent %s to process group %s\n' "$signal" "$LAB_PGID"
}

stop_lab() {
  require_safe_root
  if ! is_running; then
    rm -f "$PID_FILE" "$PGID_FILE"
    printf 'Process tree already stopped\n'
    return 0
  fi

  read_isolated_ids
  kill -TERM -- "-$LAB_PGID" 2>/dev/null || true

  for _ in 1 2 3 4 5; do
    kill -0 "$LAB_PID" 2>/dev/null || break
    sleep 1
  done
  if kill -0 "$LAB_PID" 2>/dev/null; then
    kill -KILL -- "-$LAB_PGID" 2>/dev/null || true
  fi
  rm -f "$PID_FILE" "$PGID_FILE"
  printf 'Process tree stopped\n'
}

reset_lab() {
  require_safe_root
  [[ -f "$MARKER" ]] || { printf 'Refusing reset without marker: %s\n' "$LAB_ROOT" >&2; exit 2; }
  stop_lab
  rm -rf --one-file-system "$LAB_ROOT"
  printf 'Lab removed: %s\n' "$LAB_ROOT"
}

case "${1:-}" in
  setup) setup_lab ;;
  start) start_lab ;;
  status) show_status ;;
  signal) signal_lab "${2:-}" ;;
  stop) stop_lab ;;
  reset) reset_lab ;;
  *) usage; exit 2 ;;
esac
