#!/usr/bin/env bash
#
# onx-system-update — dnf package update runner
#
# Stdin:  JSON {
#           "packages": ["httpd","mariadb-server"],  // empty = update all
#           "security_only": true|false
#         }
# Stdout: JSON {
#           "updated_count": N,
#           "packages_updated": ["name-version",...],
#           "reboot_required": true|false,
#           "output_tail": "..."
#         }
# Exit:   0=ok  1=invalid_input  3=execution_fail
#
# NOTE: dnf can take several minutes. SYSAPI_TIMEOUT should be >= 600s for full updates.

set -euo pipefail

die_input() { printf '{"error":"%s","code":1}\n' "$*" >&2; exit 1; }
die_exec()  { printf '{"error":"%s","code":3}\n' "$*" >&2; exit 3; }
json_str()  { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }

# ---------------------------------------------------------------------------
# Parse stdin
# ---------------------------------------------------------------------------
INPUT=$(cat)

SECURITY_ONLY=$(echo "$INPUT" | grep -oP '"security_only"\s*:\s*\K(true|false)' 2>/dev/null | head -1 || echo "false")

# Parse package list — simple extraction assuming no special chars
PACKAGES_RAW=$(echo "$INPUT" | grep -oP '"packages"\s*:\s*\[\K[^\]]*' 2>/dev/null || true)
PACKAGES=()
while IFS= read -r pkg; do
  pkg=$(echo "$pkg" | tr -d '"' | tr -d ' ')
  [[ -z "$pkg" ]] && continue
  # Whitelist: only alphanumeric, hyphen, dot, underscore
  [[ "$pkg" =~ ^[a-zA-Z0-9._-]+$ ]] || die_input "Gecersiz paket adi: ${pkg}"
  PACKAGES+=("$pkg")
done < <(echo "$PACKAGES_RAW" | tr ',' '\n')

# ---------------------------------------------------------------------------
# Progress / detach (additive — see PushUpdateProgress contract)
# ---------------------------------------------------------------------------
PROGRESS_FILE=$(printf '%s' "$INPUT" | jq -r '.progress_file // empty')
DETACH=$(printf '%s' "$INPUT" | jq -r '.detach // false')
JOB_ID=$(printf '%s' "$INPUT" | jq -r '.job_id // 0')
OLD_VERSION=""; NEW_VERSION=""; UPDATE_ERROR=""

_progress() {  # pct step [status]
    [[ -z "${PROGRESS_FILE:-}" ]] && return 0
    local pct="$1" step="$2" status="${3:-running}"
    local tmp="${PROGRESS_FILE}.tmp.$$"
    jq -nc --argjson pct "${pct:-0}" --arg step "$step" --arg status "$status" \
        --arg ov "${OLD_VERSION:-}" --arg nv "${NEW_VERSION:-}" --arg err "${UPDATE_ERROR:-}" \
        '{status:$status,percent:$pct,step:$step,message:"",old_version:$ov,new_version:$nv,error:$err}' \
        > "$tmp" 2>/dev/null && mv -f "$tmp" "$PROGRESS_FILE" && chmod 0644 "$PROGRESS_FILE" 2>/dev/null || true
}

# ---------------------------------------------------------------------------
# Detach: background self and return within ~1s (non-blocking job runner)
# ---------------------------------------------------------------------------
if [[ "$DETACH" == "true" || "$DETACH" == "1" ]]; then
    relaunch=$(printf '%s' "$INPUT" | jq -c '.detach=false')
    tmp=$(mktemp /tmp/onox-update-relaunch.XXXXXX.json)
    printf '%s' "$relaunch" > "$tmp"
    logf="${PROGRESS_FILE:-/tmp/onox-update}.log"
    setsid bash -c "'$0' < '$tmp'; rm -f '$tmp'" </dev/null >>"$logf" 2>&1 &
    disown 2>/dev/null || true
    printf '{"ok":true,"started":true,"job_id":%s}\n' "${JOB_ID:-0}"
    exit 0
fi

# ---------------------------------------------------------------------------
# Build dnf command
# ---------------------------------------------------------------------------
# --nobest: cakisan paketleri (orn. nodesource nodejs 22 vs appstream module 20 +
# nodejs-full-i18n) "en iyi" yerine "kurulabilir" surumde birakir -> kalan tum
# paketler guvenle guncellenir, tek bir cakisma butun upgrade'i patlatmaz.
DNF_CMD=(dnf upgrade -y --assumeyes --nobest)

if [[ "$SECURITY_ONLY" == "true" ]]; then
  DNF_CMD+=(--security)
fi

if [[ "${#PACKAGES[@]}" -gt 0 ]]; then
  DNF_CMD+=("${PACKAGES[@]}")
fi

_progress 10 "paket listesi"

# ---------------------------------------------------------------------------
# Run update, capture output
# ---------------------------------------------------------------------------
TMPOUT=$(mktemp /tmp/onx-system-update-XXXXXX.log)
trap 'rm -f "$TMPOUT"' EXIT

_progress 30 "indiriliyor"

EXIT_CODE=0
"${DNF_CMD[@]}" 2>&1 | tee "$TMPOUT" || EXIT_CODE=$?

if [[ "$EXIT_CODE" -ne 0 && "$EXIT_CODE" -ne 100 ]]; then
  # dnf exits 100 when "nothing to do" — treat as ok.
  # dnf transactions are atomic — no manual undo (history undo can break deps).
  TAIL=$(tail -8 "$TMPOUT" | tr '\n' ' ' | sed 's/  */ /g')
  UPDATE_ERROR="dnf hata (exit ${EXIT_CODE}): ${TAIL}"
  _progress 80 "basarisiz" "failed"
  die_exec "$(json_str "dnf basarisiz (exit ${EXIT_CODE}): ${TAIL}")"
fi

_progress 80 "kuruluyor"

# ---------------------------------------------------------------------------
# Parse updated packages from dnf output
# ---------------------------------------------------------------------------
UPDATED_LIST=()
while IFS= read -r line; do
  # Lines like: "  httpd.x86_64  2.4.63-1.el9  AppStream"
  # or "Upgraded: httpd-2.4.63-1.el9.x86_64"
  if echo "$line" | grep -qP '^\s+\S+\s+\S+-\S+\s+\S+'; then
    PKG_NAME=$(echo "$line" | awk '{print $1}' | sed 's/\..*//')
    PKG_VER=$(echo "$line" | awk '{print $2}')
    [[ -n "$PKG_NAME" ]] && UPDATED_LIST+=("${PKG_NAME}-${PKG_VER}")
  fi
done < <(grep -P '^\s+\S+\s+\S+\s+\S+' "$TMPOUT" 2>/dev/null | head -100 || true)

COUNT=${#UPDATED_LIST[@]}

# Fallback: count "Upgraded" lines
if [[ "$COUNT" -eq 0 ]]; then
  COUNT=$(grep -c '^Upgraded:' "$TMPOUT" 2>/dev/null || echo "0")
fi

# ---------------------------------------------------------------------------
# Reboot required? (kernel, glibc, systemd updates)
# ---------------------------------------------------------------------------
REBOOT_REQUIRED=false
if grep -qiP 'kernel|glibc|systemd|dbus' "$TMPOUT" 2>/dev/null; then
  # Only flag reboot if one of those was actually updated
  if grep -qiP '^Upgraded:.*kernel|^Upgraded:.*glibc|^Upgraded:.*systemd' "$TMPOUT" 2>/dev/null; then
    REBOOT_REQUIRED=true
  fi
  # Also check /run/reboot-required (debian style, sometimes present via plugins)
  [[ -f /run/reboot-required ]] && REBOOT_REQUIRED=true
fi

# Build JSON array for packages_updated
PKG_JSON="["
FIRST=1
for p in "${UPDATED_LIST[@]:0:50}"; do  # cap at 50 entries
  [[ "$FIRST" -eq 0 ]] && PKG_JSON="${PKG_JSON},"
  PKG_JSON="${PKG_JSON}\"$(json_str "$p")\""
  FIRST=0
done
PKG_JSON="${PKG_JSON}]"

# Last 3 lines of dnf output for context
OUTPUT_TAIL=$(tail -3 "$TMPOUT" | tr '\n' ' ' | sed 's/  */ /g')

_progress 100 "tamamlandi" "completed"

printf '{"updated_count":%s,"packages_updated":%s,"reboot_required":%s,"output_tail":"%s"}\n' \
  "$COUNT" \
  "$PKG_JSON" \
  "$REBOOT_REQUIRED" \
  "$(json_str "$OUTPUT_TAIL")"

exit 0
