#!/usr/bin/env bash
# Task 569 — stale-lockdir TTL reclamation in _hook_acquire_lock and
# _drain_acquire_lock. A backdated lockdir is force-removed on acquire;
# a freshly held lockdir is not.
set -u

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LIB="$ROOT/lib/hook-emit.sh"
DRAIN="$ROOT/post-tool-use-agent.sh"
[[ -f "$LIB" ]] || { echo "FAIL: $LIB missing"; exit 1; }
[[ -f "$DRAIN" ]] || { echo "FAIL: $DRAIN missing"; exit 1; }

TMP=$(mktemp -d)
export MAXY_HOOK_BUFFER_DIR="$TMP/buffers"
export MAXY_HOOK_SERVER_LOG="$TMP/server.log"
# Tight test budget — 20 × 10ms = 200ms ceiling so the negative case
# returns quickly.
export MAXY_HOOK_LOCK_ATTEMPTS=20
export MAXY_HOOK_LOCK_SLEEP=0.01
export MAXY_HOOK_LOCK_TTL_SECONDS=2
mkdir -p "$MAXY_HOOK_BUFFER_DIR"

PASS=0; FAIL=0
pass() { PASS=$((PASS+1)); echo "PASS: $1"; }
fail() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }

# shellcheck disable=SC1090
source "$LIB" || { fail "source library"; exit 1; }
pass "source library"

# Test 1: a stale lockdir (mtime backdated 10s; TTL=2s) is reclaimed and
# acquire succeeds.
LOCK1="$TMP/lock-stale"
mkdir "$LOCK1"
# Backdate mtime by 10s. -t needs YYYYMMDDhhmm.SS form; portable form
# uses -d on GNU and -A/-r on BSD, so we use python to set it.
python3 -c "import os,time; os.utime('$LOCK1', (time.time()-10, time.time()-10))"
if _hook_acquire_lock "$LOCK1"; then
  pass "stale lockdir reclaimed (emit-path)"
else
  fail "stale lockdir not reclaimed within budget (emit-path)"
fi
# Verify observability line.
if grep -q "\[hook-propagate-error\] reason=stale-lock-reclaimed lockdir=$LOCK1" "$MAXY_HOOK_SERVER_LOG"; then
  pass "stale-lock-reclaimed observability line emitted"
else
  fail "stale-lock-reclaimed line missing from server.log"
fi
rmdir "$LOCK1" 2>/dev/null || true

# Test 2: a fresh lockdir (mtime = now) is NOT reclaimed and acquire
# returns 1 within the attempt budget.
LOCK2="$TMP/lock-fresh"
mkdir "$LOCK2"
START=$(date +%s)
if _hook_acquire_lock "$LOCK2"; then
  fail "fresh lockdir wrongly reclaimed (emit-path)"
else
  END=$(date +%s)
  pass "fresh lockdir not reclaimed; acquire timed out (emit-path, ${END}-${START}s)"
fi
# Verify no stale-lock-reclaimed line was written for LOCK2.
if grep -q "lockdir=$LOCK2 ageSeconds=" "$MAXY_HOOK_SERVER_LOG"; then
  fail "fresh lockdir wrongly logged as stale (emit-path)"
else
  pass "no stale-lock log for fresh lockdir (emit-path)"
fi
rmdir "$LOCK2" 2>/dev/null || true

# Test 3: same property for the drainer-side function. Extract
# _drain_acquire_lock + its age helper from post-tool-use-agent.sh and
# source them in isolation.
DRAIN_LIB="$TMP/drain-lock.sh"
awk '
  /^_drain_lockdir_age_seconds\(\)/,/^}/ { print; next }
  /^_drain_acquire_lock\(\)/,/^}/ { print; next }
' "$DRAIN" > "$DRAIN_LIB"
[[ -s "$DRAIN_LIB" ]] || { fail "drain lock extract empty"; }
# shellcheck disable=SC1090
source "$DRAIN_LIB" || fail "source drain lib"

LOCK3="$TMP/lock-drain-stale"
mkdir "$LOCK3"
python3 -c "import os,time; os.utime('$LOCK3', (time.time()-10, time.time()-10))"
if _drain_acquire_lock "$LOCK3"; then
  pass "stale lockdir reclaimed (drain-path)"
else
  fail "stale lockdir not reclaimed (drain-path)"
fi

LOCK4="$TMP/lock-drain-fresh"
mkdir "$LOCK4"
if _drain_acquire_lock "$LOCK4"; then
  fail "fresh lockdir wrongly reclaimed (drain-path)"
else
  pass "fresh lockdir not reclaimed; acquire timed out (drain-path)"
fi

rm -rf "$TMP"
echo "----"
echo "$PASS pass, $FAIL fail"
[[ "$FAIL" -eq 0 ]]
