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

SRC_DIR="/usr/share/cmk-push-agent/plugins"
AGENT_DIR="/usr/lib/check_mk_agent/plugins"
CACHE_DIR="${CACHE_DIR:-/var/lib/check_mk_agent/cache}"

# Ausfuehrungsintervall in Sekunden. Der Agent leitet es aus dem Namen des
# Unterverzeichnisses ab: Plugins in plugins/3600/ werden hoechstens stuendlich
# ausgefuehrt, dazwischen liefert er den zwischengespeicherten Output.
PLUGIN_INTERVAL="${PLUGIN_INTERVAL:-3600}"

# Einzelne Plugins koennen ein abweichendes Intervall anfordern, indem sie in
# den ersten 20 Zeilen eine Zeile der Form
#   # cmk-cache-interval: 300
# fuehren. Ohne diese Zeile gilt PLUGIN_INTERVAL.
INTERVAL_HEADER_LINES=20

log()  { printf '[%s] %s\n' "$(date -Is)" "$*" >&2; }
warn() { log "WARN: $*"; }
die()  { log "ERROR: $*"; exit 2; }

[[ -d "$SRC_DIR" ]]   || die "Source dir not found: $SRC_DIR"
[[ -d "$AGENT_DIR" ]] || die "Agent plugins dir not found: $AGENT_DIR"
[[ "$PLUGIN_INTERVAL" =~ ^[0-9]+$ ]] \
  || die "PLUGIN_INTERVAL must be a number of seconds, got: $PLUGIN_INTERVAL"

is_candidate() {
  local f="$1"
  [[ -f "$f" && -r "$f" ]] || return 1
  case "$(basename -- "$f")" in
    .*|*~|*.bak|*.disabled) return 1 ;;
  esac
  return 0
}

run_detect() {
  local plugin_file="$1"
  # Detect must be executed via --detect and only exit code matters.
  # Stdout/stderr are suppressed (but you can remove redirections for debugging).
  "$plugin_file" --detect >/dev/null 2>&1
}

# Intervall fuer ein einzelnes Plugin ermitteln.
plugin_interval() {
  local f="$1" v=""
  v="$(sed -n "1,${INTERVAL_HEADER_LINES}{s/^#[[:space:]]*cmk-cache-interval:[[:space:]]*\([0-9]\+\).*/\1/p}" \
       -- "$f" 2>/dev/null | head -n1)"
  if [[ "$v" =~ ^[0-9]+$ ]] && (( v > 0 )); then
    printf '%s\n' "$v"
  else
    printf '%s\n' "$PLUGIN_INTERVAL"
  fi
}

# Alle Orte, an denen fuer dieses Plugin ein von uns verwalteter Link liegen
# koennte: direkt in AGENT_DIR (= ungecacht, der alte Zustand) und in jedem
# Intervall-Unterverzeichnis.
managed_locations() {
  local name="$1" d
  printf '%s\n' "$AGENT_DIR/$name"
  for d in "$AGENT_DIR"/[0-9]*; do
    [[ -d "$d" ]] || continue
    printf '%s\n' "$d/$name"
  done
  return 0
}

# Entfernt einen Link nur, wenn er in unser SRC_DIR zeigt.
remove_managed_link() {
  local lp="$1" current
  [[ -L "$lp" ]] || return 1
  current="$(readlink -- "$lp" || true)"
  if [[ "$current" == "$SRC_DIR/"* ]]; then
    rm -f -- "$lp"
    return 0
  fi
  warn "Not removing $lp: symlink points outside managed dir ($current)"
  return 1
}

# Cachedatei eines deaktivierten Plugins wegraeumen. Ohne das liefert der Agent
# den alten Output noch bis zum Ablauf des Intervalls weiter - bei 3600s also
# bis zu einer Stunde lang Daten zu einer Anwendung, die es nicht mehr gibt.
# Die Namensschemata unterscheiden sich je nach Agentversion, deshalb werden
# beide gaengigen probiert; trifft keines zu, passiert nichts.
drop_cache() {
  local name="$1" c
  for c in "$CACHE_DIR/plugins_$name.cache" "$CACHE_DIR/local_$name.cache"; do
    if [[ -f "$c" ]]; then
      rm -f -- "$c"
      log "CACHE dropped: $c"
    fi
  done
  return 0
}

# Raeumt Links fuer dieses Plugin an allen Orten ausser $keep weg. Das deckt
# zwei Faelle ab: die Migration vom ungecachten Zustand (Link direkt in
# AGENT_DIR) und einen geaenderten Intervallwert (alter Link in 3600/, neuer
# in 1800/). Bleibt der alte Link liegen, fuehrt der Agent das Plugin doppelt
# aus und die Section erscheint zweimal im Output.
purge_other_locations() {
  local name="$1" keep="$2" other
  while read -r other; do
    if [[ "$other" == "$keep" ]] || [[ ! -L "$other" ]]; then
      continue
    fi
    if remove_managed_link "$other"; then
      log "MIGRATED: removed stale link $other"
    fi
  done < <(managed_locations "$name")
  return 0
}

ensure_enabled() {
  local plugin_file="$1"
  local name interval target linkpath current
  name="$(basename -- "$plugin_file")"
  interval="$(plugin_interval "$plugin_file")"
  target="$AGENT_DIR/$interval"
  linkpath="$target/$name"

  if [[ ! -d "$target" ]]; then
    mkdir -p -- "$target" || { warn "Cannot create $target"; return 0; }
    log "CREATED interval dir: $target"
  fi

  purge_other_locations "$name" "$linkpath"

  # If link exists, ensure it points to our managed source.
  if [[ -L "$linkpath" ]]; then
    current="$(readlink -- "$linkpath" || true)"
    if [[ "$current" != "$plugin_file" ]]; then
      rm -f -- "$linkpath"
      ln -s -- "$plugin_file" "$linkpath"
      log "ENABLED (updated link): $name [${interval}s]"
    else
      log "ENABLED (already): $name [${interval}s]"
    fi
    return 0
  fi

  # If a regular file exists there, do not overwrite.
  if [[ -e "$linkpath" && ! -L "$linkpath" ]]; then
    warn "Cannot enable $name: $linkpath exists and is not a symlink"
    return 0
  fi

  ln -s -- "$plugin_file" "$linkpath"
  log "ENABLED: $name [${interval}s]"
}

ensure_disabled() {
  local plugin_file="$1"
  local name other removed=0
  name="$(basename -- "$plugin_file")"

  while read -r other; do
    [[ -L "$other" ]] || continue
    if remove_managed_link "$other"; then
      log "DISABLED: $name ($other)"
      removed=1
    fi
  done < <(managed_locations "$name")

  if (( removed )); then
    drop_cache "$name"
  else
    log "DISABLED (already): $name"
  fi
  return 0
}

# Leere Intervallverzeichnisse aufraeumen, damit nach einer Intervalaenderung
# keine verwaisten Ordner stehen bleiben.
prune_empty_interval_dirs() {
  local d
  for d in "$AGENT_DIR"/[0-9]*; do
    [[ -d "$d" ]] || continue
    if rmdir -- "$d" 2>/dev/null; then
      log "PRUNED empty interval dir: $d"
    fi
  done
  return 0
}

# Deterministic iteration
mapfile -d '' plugins < <(find "$SRC_DIR" -maxdepth 1 -type f -print0 | sort -z)
if [[ ${#plugins[@]} -eq 0 ]]; then
  log "No plugin files found in $SRC_DIR"
  exit 0
fi

for f in "${plugins[@]}"; do
  is_candidate "$f" || continue
  # Require executable, otherwise detect/agent execution won't work.
  if [[ ! -x "$f" ]]; then
    warn "Skipping $(basename -- "$f"): not executable (chmod 0755)"
    continue
  fi
  if run_detect "$f"; then
    ensure_enabled "$f"
  else
    rc=$?
    if [[ "$rc" -eq 1 ]]; then
      ensure_disabled "$f"
    else
      warn "detect returned $rc for $(basename -- "$f") -> disabling for safety"
      ensure_disabled "$f"
    fi
  fi
done

prune_empty_interval_dirs