#!/bin/bash
# ==============================================================================================
# ============================= User Scripts Stop ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Stops all running User Script processes spawned by the unRAID User Scripts
# plugin. Shows script names not just PIDs so you know what's being stopped.
# Called automatically by server_reboot.sh as part of the shutdown sequence,
# and useful directly when a script is stuck and won't respond to the UI.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Process Identification
#   Scans /proc/*/cmdline for processes whose command line contains
#   "/tmp/user.scripts". The User Scripts plugin stages all scripts in
#   /tmp/user.scripts/ before execution — more reliable than process name
#   matching which can vary.
#
# Stop Sequence Per Process
#   1. Send SIGTERM — allows the script to trap and clean up gracefully
#   2. Wait 5 seconds
#   3. If still running → SIGKILL (force)
#   4. Verify dead after SIGKILL — error if still running
#
# Self-Exclusion
#   If this script is run via the User Scripts plugin it would find its own
#   PID in the scan. Self-exclusion by PID prevents killing its own process
#   tree mid-execution.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Name the Scripts, Not Just the PIDs
#   Output resolves each process to the script it is running. "Stopping 4 processes" tells
#   an operator nothing they can act on; "stopping mover_tuning, preclear" tells them
#   exactly what they are about to lose and whether they should wait.
#
# Targeted, Never Blanket
#   Only processes spawned by the User Scripts plugin are matched. A broad pattern would
#   catch unrelated shells and Varaverk's own scripts — including, during a reboot sequence,
#   the very script doing the stopping.
#
# Shutdown Step and Manual Tool, Same Behaviour
#   Called automatically by server_reboot.sh and usable by hand on a stuck script. It takes
#   no "reboot mode" — the correct action is identical either way, and a mode flag would be
#   a second code path that only ever runs unattended.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   kill requires root for other users' processes.
#
# Single Instance Lock
#   acquire_lock prevents concurrent stop attempts.
#
# SIGTERM → SIGKILL Sequence
#   Graceful first. Forced only if SIGTERM ignored after 5 seconds.
#
# Post-Kill Verify
#   Confirms each process is actually dead. Errors and notifies if unkillable.
#
# Silent When Clean
#   No processes running = log() only, no visible output.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# No config vars. Targets are discovered from running processes rather than from a list,
# because the User Scripts plugin owns what exists and any list here would go stale.
#
# Platform-owned:
#
#   The unRAID User Scripts plugin's script directory and the processes it spawns. Matching
#   is scoped to those — see Targeted, Never Blanket above for why that matters during a
#   reboot sequence.
#
# Called by server_reboot.sh as a shutdown step; also safe to run by hand.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# user_scripts_stop.sh
#     Find and stop all User Script processes. Silent if none running.
#
# user_scripts_stop.sh --dry-run
#     Show which processes would be stopped, with names and runtimes. No kills.
#
# user_scripts_stop.sh --status
#     Show currently running User Script processes with names and elapsed time.
#
# user_scripts_stop.sh --log
#     Verbose output — show each process found, each signal sent, each result.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../../../load_config.sh"

parse_args "$@"

MY_PID=$$
MY_PPID=$PPID

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root — kill requires root for other users' processes"
    exit 1
fi

platform_require_cmd \
    "/usr/local/emhttp/plugins/dynamix/scripts/notify" \
    "" "" \
    "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"

acquire_lock

detect_hosts

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no processes will be killed"

# ==============================================================================================
# ── HELPER FUNCTIONS ──────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# Get script name from PID — extracts meaningful name from /tmp/user.scripts path
get_script_name() {
    local pid="$1"
    local cmdline
    cmdline=$(tr '\0' ' ' < /proc/"$pid"/cmdline 2>/dev/null || echo "")
    # Extract the script filename from the /tmp/user.scripts/... path
    echo "$cmdline" | grep -o '/tmp/user\.scripts[^ ]*' | \
        awk -F/ '{print $NF}' | head -1 || echo "pid-$pid"
}

# Get all user script PIDs — excludes self and own parent process tree
get_user_script_pids() {
    local -a pids=()
    while IFS= read -r pid; do
        [[ -z "$pid" ]] && continue
        # Self-exclusion — don't kill our own process or parent
        [[ "$pid" == "$MY_PID" ]] && continue
        [[ "$pid" == "$MY_PPID" ]] && continue
        pids+=("$pid")
    done < <(
        for dir in /proc/[0-9]*/cmdline; do
            pid="${dir%/cmdline}"
            pid="${pid#/proc/}"
            if grep -ql '/tmp/user\.scripts' "$dir" 2>/dev/null; then
                echo "$pid"
            fi
        done
    )
    (( ${#pids[@]} > 0 )) && printf '%s\n' "${pids[@]}"
}

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY USER SCRIPTS STATUS ━━━━━"
    echo "$ICON_HOST   Identity: $MY_ID ($LOCAL_SERVER_NAME)"
    echo ""

    mapfile -t PIDS < <(get_user_script_pids)

    if [[ ${#PIDS[@]} -eq 0 ]]; then
        log "No User Script processes running"
    else
        echo "  ${#PIDS[@]} User Script process(es) running:"
        for pid in "${PIDS[@]}"; do
            name=$(get_script_name "$pid")
            elapsed=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
            runtime=$(format_duration "${elapsed:-0}")
            echo "  $ICON_RUNNING PID $pid — $name (${runtime})"
        done
    fi
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

# ==============================================================================================
# ━━━ User Scripts Stop ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_PLUGIN User Scripts Stop — $MY_ID ━━━"

START=$(date +%s)

mapfile -t PIDS < <(get_user_script_pids)

KILLED=()
FAILED=()
SKIPPED=()

if [[ ${#PIDS[@]} -eq 0 ]]; then
    echo "No User Script processes running — nothing to do"
else
    warn "${#PIDS[@]} User Script process(es) found"
    echo ""

    for pid in "${PIDS[@]}"; do
        name=$(get_script_name "$pid")

        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would stop: $name (PID $pid)"
            SKIPPED+=("$name")
            continue
        fi

        # Verify still running before trying to kill
        if ! kill -0 "$pid" 2>/dev/null; then
            log "$name (PID $pid) — already exited"
            continue
        fi

        # SIGTERM — graceful stop
        log "Sending SIGTERM to $name (PID $pid)..."
        kill -TERM "$pid" 2>/dev/null || true
        sleep 5

        # Check if stopped after SIGTERM
        if ! kill -0 "$pid" 2>/dev/null; then
            warn "Stopped: $name (PID $pid) ✅"
            KILLED+=("$name")
            continue
        fi

        # SIGKILL — forced stop
        warn "$name still running after SIGTERM — sending SIGKILL"
        kill -KILL "$pid" 2>/dev/null || true
        sleep 2

        # Final verify
        if ! kill -0 "$pid" 2>/dev/null; then
            warn "Force-stopped: $name (PID $pid) ✅"
            KILLED+=("$name")
        else
            error "Failed to kill: $name (PID $pid)"
            FAILED+=("$name")
        fi
    done
fi

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY USER SCRIPTS STOP SUMMARY ━━━━━"
echo "$ICON_HOST   Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME   Duration: $(format_duration $(( END - START )))"
echo ""

if [[ ${#PIDS[@]} -eq 0 ]]; then
    echo "No processes were running"
elif [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would have stopped ${#SKIPPED[@]} process(es): ${SKIPPED[*]}"
else
    [[ ${#KILLED[@]} -gt 0 ]] && warn "Stopped (${#KILLED[@]}): ${KILLED[*]}"
    [[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed (${#FAILED[@]}): ${FAILED[*]}"
fi

echo ""
if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — no changes made"
elif [[ ${#FAILED[@]} -gt 0 ]]; then
    echo "$ICON_ERROR Status: SOME PROCESSES COULD NOT BE KILLED"
    notify "User Scripts stop failed on $(hostname) ($MY_ID) — unkillable: ${FAILED[*]}" \
        "User Scripts Stop" "warning"
else
    echo "$ICON_DONE Status: done ✅"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

[[ ${#FAILED[@]} -gt 0 ]] && exit 1
exit 0