#!/usr/bin/env bash
# Exercise 3: a safe release script — args, dry-run, locking, idempotent symlink switch (hard)
# Run with: bash linux_bash_scripting_exercise_3.sh            (runs the self-check)
#
# Implement `deploy` — a classic "releases + current symlink" deployment:
#   BASE/releases/<version>/   one directory per release
#   BASE/current -> releases/<version>   (atomic symlink switch)
#
# deploy [-n] [-k KEEP] -b BASE -v VERSION -s SOURCE_DIR
#   -b BASE      base directory (required)
#   -v VERSION   semantic version like 1.4.2 (required; reject anything else with exit 2)
#   -s SOURCE    directory whose contents are copied into the release (required)
#   -k KEEP      how many releases to keep in total (default 3): the one `current`
#                points to plus the KEEP-1 newest others; the rest are deleted
#   -n           dry-run: print "DRY-RUN: <action>" lines and change nothing
#
# Behaviour:
#   1. Parse options with getopts; on a missing required option print a usage line
#      to stderr and return 2.
#   2. Take a lock BASE/.deploy.lock using mkdir (portable; flock isn't on macOS) —
#      if the lock already exists, print "locked" to stderr and return 3. Always
#      release the lock on return (success or failure).
#   3. If releases/VERSION already exists, don't copy again (idempotent) — just
#      switch the symlink.
#   4. Switch `current` to releases/VERSION (relative link target) and print
#      "current -> VERSION". On Linux the atomic way is
#      `ln -s releases/V current.tmp && mv -T current.tmp current`; `ln -sfn` is
#      fine for this exercise (macOS has no `mv -T`).
#   5. Prune old releases beyond KEEP (sort versions numerically; the current
#      release always survives and counts towards KEEP).
#
# Must work on bash 3.2.

set -uo pipefail

deploy() {
  # TODO
  :
}

# --- self-check: do not modify below this line ---
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
}
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
mkdir -p "$T/src"; echo "v1" > "$T/src/app.txt"
B="$T/app"; mkdir -p "$B"
cur() { [[ -L "$B/current" ]] && basename "$(readlink "$B/current")" || echo none; }
rels() { ls "$B/releases" 2>/dev/null | sort -t. -k1,1n -k2,2n -k3,3n | tr '\n' ' ' | sed 's/ $//'; }

deploy -b "$B" -v 1.0.0 >/dev/null 2>&1; check "missing -s → exit 2" "$?" "2"
deploy -b "$B" -v latest -s "$T/src" >/dev/null 2>&1; check "bad version → exit 2" "$?" "2"

out="$(deploy -n -b "$B" -v 1.0.0 -s "$T/src" 2>&1)"
check "dry-run prints actions" "$(printf '%s\n' "$out" | grep -c '^DRY-RUN: ' | awk '{print ($1>0)?"yes":"no"}')" "yes"
check "dry-run changes nothing" "$(cur) [$(rels)]" "none []"

check "deploy 1.0.0" "$(deploy -b "$B" -v 1.0.0 -s "$T/src" 2>/dev/null)" "current -> 1.0.0"
check "release content copied" "$(cat "$B/current/app.txt" 2>/dev/null)" "v1"
echo "v2" > "$T/src/app.txt"
deploy -b "$B" -v 1.1.0 -s "$T/src" >/dev/null 2>&1
check "current switched to 1.1.0" "$(cur)" "1.1.0"
echo "CHANGED" > "$T/src/app.txt"
deploy -b "$B" -v 1.0.0 -s "$T/src" >/dev/null 2>&1
check "redeploy existing version is idempotent (no re-copy)" "$(cat "$B/current/app.txt" 2>/dev/null)" "v1"
check "rollback switched current" "$(cur)" "1.0.0"
for v in 1.2.0 1.10.0 1.9.0; do deploy -b "$B" -v "$v" -s "$T/src" >/dev/null 2>&1; done
check "prune keeps 3 newest (numeric sort)" "$(rels)" "1.2.0 1.9.0 1.10.0"
deploy -k 2 -b "$B" -v 1.0.0 -s "$T/src" >/dev/null 2>&1
check "prune never deletes current" "$(cur) [$(rels)]" "1.0.0 [1.0.0 1.10.0]"
check "lock released after deploy" "$([[ -e "$B/.deploy.lock" ]] && echo held || echo free)" "free"
mkdir "$B/.deploy.lock"
deploy -b "$B" -v 1.10.0 -s "$T/src" >/dev/null 2>&1; check "concurrent deploy → exit 3" "$?" "3"
check "foreign lock left alone" "$([[ -d "$B/.deploy.lock" ]] && echo yes || echo no)" "yes"

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