#!/usr/bin/env bash
#
# HostSeba NOC Agent — Phase 2 on-demand probe runner.
#
# Sourced by agent.sh. Long-polls /api/agent/probes/pending; for each
# returned probe, runs the template the BACKEND sent us (NOT a local
# decision), capturing stdout/stderr/exit/duration, and posts back.
#
# Safety model:
#   1. We do NOT pick the command — the backend's AgentProbeRegistry is
#      the only place templates live. We just substitute parameter
#      placeholders and run.
#   2. Each parameter is wrapped in single quotes with internal single
#      quotes escaped via '"'"' — neutralises shell-injection attempts
#      that slipped past the API's regex validators.
#   3. Each probe is run with a hard timeout (max_runtime from registry)
#      via `timeout` so a stuck command can't lock the agent.
#   4. Output is capped at max_output_bytes.
#   5. Each probe gets its own subshell so a `set -e` / `exit` inside
#      a template can't kill the loop.

# Long-poll for pending probes targeting this server. `wait=20` keeps
# the round-trip count low while staying responsive.
poll_probes() {
    local resp
    resp=$(curl -sS -m 30 \
        -H "X-Server-Id: ${SERVER_ID}" \
        -H "X-Agent-Secret: ${AGENT_SECRET}" \
        -H "Accept: application/json" \
        "${BACKEND_URL}/api/agent/probes/pending?wait=20&max=3" 2>/dev/null)
    [ -z "$resp" ] && return 0

    # Extract count of probes from the JSON. jq is required (already a
    # dependency of agent.sh for the metric collectors).
    local count
    count=$(echo "$resp" | jq '.probes | length' 2>/dev/null)
    [ -z "$count" ] || [ "$count" = "0" ] && return 0

    local i
    for ((i=0; i<count; i++)); do
        run_single_probe "$(echo "$resp" | jq ".probes[$i]")"
    done
}

# Execute one probe, capture result, post back. Argument is the
# JSON object for ONE probe from the pending response.
run_single_probe() {
    local probe="$1"
    local id        ; id=$(echo "$probe"        | jq -r '.id')
    local template  ; template=$(echo "$probe"  | jq -r '.template')
    local max_run   ; max_run=$(echo "$probe"   | jq -r '.max_runtime')
    local max_bytes ; max_bytes=$(echo "$probe" | jq -r '.max_output_bytes')

    # Substitute parameters into the template. We walk the params JSON
    # one key at a time; for each, single-quote-wrap the value (with
    # internal-quote escape) and replace the {{key}} placeholder.
    #
    # The regex / url / process_name validators on the API side already
    # rejected dangerous values — this is a second layer for tickets
    # that found a way past the validator (zero-day, future bug).
    local params_keys
    params_keys=$(echo "$probe" | jq -r '.params | keys[]?' 2>/dev/null)
    local cmd="$template"
    if [ -n "$params_keys" ]; then
        while IFS= read -r key; do
            local raw
            raw=$(echo "$probe" | jq -r ".params[\"$key\"]")
            # Single-quote shell-escape:
            #   foo => 'foo'
            #   a'b => 'a'"'"'b'
            local quoted="'${raw//\'/\'\"\'\"\'}'"
            # Replace {{key}} with the quoted value. printf %s avoids
            # bash interpreting backslash sequences in the replacement.
            cmd="${cmd//\{\{$key\}\}/$quoted}"
        done <<< "$params_keys"
    fi

    # Run with hard timeout + capture. We use `bash -c "$cmd"` so the
    # template can include pipes, redirects, etc. The single-quote
    # wrapping above means params are NOT interpolated by bash — they're
    # literal strings.
    local started=$(date +%s%N)
    local output
    local exit_code
    output=$(timeout "${max_run}s" bash -c "$cmd" 2>&1)
    exit_code=$?
    local finished=$(date +%s%N)
    local duration_ms=$(( (finished - started) / 1000000 ))

    # Truncate output at max_bytes. head -c is bytes, not chars — fine
    # for our use (output is ASCII-ish status text from `df`, `pgrep`,
    # `curl -w`, etc.).
    if [ ${#output} -gt "$max_bytes" ]; then
        output=$(printf '%s' "$output" | head -c "$max_bytes")
        output+=$'\n[truncated at '"$max_bytes"' bytes]'
    fi

    # Build the JSON result payload. Use jq -nR --arg to safely encode
    # strings (handles newlines, quotes, backslashes correctly).
    local payload
    payload=$(jq -nR --arg out "$output" --argjson ec "$exit_code" --argjson dur "$duration_ms" '{
        exit_code: $ec,
        output: $out,
        duration_ms: $dur
    }' < /dev/null)

    curl -sS -m 15 -o /dev/null \
        -X POST \
        -H "X-Server-Id: ${SERVER_ID}" \
        -H "X-Agent-Secret: ${AGENT_SECRET}" \
        -H "Content-Type: application/json" \
        -d "$payload" \
        "${BACKEND_URL}/api/agent/probes/${id}/result" 2>/dev/null
}
