#!/usr/bin/env bash
# Exercise 1: analyze process listings and generate a hardened systemd unit (easy)
# Run with: bash linux_processes_and_systemd_exercise_1.sh
#
# Works offline: the functions read a canned `ps -eo pid,ppid,stat,rss,comm` sample
# from stdin, so the results are the same on any machine.
#
# STEP 1: top_memory      — print the COMMAND with the highest RSS (column 4)
# STEP 2: count_states    — print "STATE COUNT" lines for the first letter of STAT
#                           (R, S, D, Z, T…), sorted by state letter
# STEP 3: zombie_parents  — print the PPIDs of zombie processes (STAT starts with Z),
#                           unique, sorted numerically, one per line
# STEP 4: make_unit NAME CMD USER — print a systemd service unit that:
#           has Description=NAME, After= and Wants=network-online.target,
#           runs ExecStart=CMD as User=USER, Restart=on-failure, RestartSec=5,
#           NoNewPrivileges=true, ProtectSystem=strict, PrivateTmp=true,
#           and WantedBy=multi-user.target in an [Install] section
# STEP 5: exit_reason CODE — print "ok" for 0, "not found" for 127,
#           "signal N" for codes > 128 (e.g. 137 → "signal 9"), otherwise "error"

set -uo pipefail

top_memory() {
  # TODO: skip the header line, sort by RSS, print the command
  :
}

count_states() {
  # TODO: first letter of column 3 → counts, e.g. "R 1"
  :
}

zombie_parents() {
  # TODO
  :
}

make_unit() {
  local name="$1" cmd="$2" user="$3"
  # TODO: print the unit file (a heredoc works well)
  : "$name" "$cmd" "$user"
}

exit_reason() {
  # TODO
  :
}

# --- self-check: do not modify below this line ---
SAMPLE='  PID  PPID STAT   RSS COMMAND
    1     0 Ss    12000 systemd
  410     1 Ss     8000 sshd
  900     1 Ssl  350000 node
  901   900 Z         0 node
  902   900 Z         0 node
 1200     1 Sl   910000 java
 1300  1200 D     20000 java
 1400     1 R     15000 python3
 1500  1400 Z         0 sh
 1600     1 T      3000 vim'

all_ok=true
check() {
  if [[ "$2" == "$3" ]]; then echo "ok   $1"; else echo "FAIL $1"; echo "     got:      $(printf '%s' "$2" | tr '\n' '|')"; echo "     expected: $(printf '%s' "$3" | tr '\n' '|')"; all_ok=false; fi
}

check "top_memory" "$(printf '%s\n' "$SAMPLE" | top_memory)" "java"
check "count_states" "$(printf '%s\n' "$SAMPLE" | count_states)" "$(printf 'D 1\nR 1\nS 4\nT 1\nZ 3')"
check "zombie_parents" "$(printf '%s\n' "$SAMPLE" | zombie_parents)" "$(printf '900\n1400')"

unit="$(make_unit petpal-api '/usr/bin/node /opt/api/server.js' petpal)"
has() { if printf '%s\n' "$unit" | grep -qxF "$1"; then echo yes; else echo "missing: $1"; fi; }
for line in '[Unit]' 'Description=petpal-api' 'After=network-online.target' 'Wants=network-online.target' \
            '[Service]' 'ExecStart=/usr/bin/node /opt/api/server.js' 'User=petpal' 'Restart=on-failure' \
            'RestartSec=5' 'NoNewPrivileges=true' 'ProtectSystem=strict' 'PrivateTmp=true' \
            '[Install]' 'WantedBy=multi-user.target'; do
  check "make_unit has '$line'" "$(has "$line")" "yes"
done

check "exit_reason 0" "$(exit_reason 0)" "ok"
check "exit_reason 127" "$(exit_reason 127)" "not found"
check "exit_reason 137" "$(exit_reason 137)" "signal 9"
check "exit_reason 143" "$(exit_reason 143)" "signal 15"
check "exit_reason 1" "$(exit_reason 1)" "error"

if $all_ok; then echo "PASS"; else echo "FAIL"; fi
