#!/usr/bin/env zsh

set -u

repo_dir="${0:A:h}"
source "$repo_dir/func"

failures=0
fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan build launcher.XXXXXX")" || exit 1
source_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan-build-source.XXXXXX")" || exit 1
export PLAN_BUILD_CACHE_DIR="$fixture_dir/cache with spaces"
export PLAN_BUILD_BASE_URL="file://$source_dir"
export HOME="$fixture_dir/home with spaces"
PLAN_BUILD_SKILLS_DIR="$HOME/.claude/skills"
mkdir -p "$source_dir" "$PLAN_BUILD_SKILLS_DIR/plan-build" "$PLAN_BUILD_SKILLS_DIR/plan-build-architect"
print -r -- "old standard" > "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md"
print -r -- "old architect" > "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md"
trap 'rm -rf -- "$fixture_dir" "$source_dir"' EXIT

fail() {
  print -u2 -- "FAIL: $1"
  failures=$((failures + 1))
}

assert_equal() {
  [[ "$1" = "$2" ]] || fail "$3 (expected '$1', got '$2')"
}

assert_contains() {
  [[ "$1" == *"$2"* ]] || fail "$3 (missing '$2')"
}

assert_file_contains() {
  command grep -Fq -- "$2" "$1" || fail "$3"
}

assert_bundle_consistent() {
  local release="$PLAN_BUILD_CACHE_DIR/current"
  local script_generation skill_generation architect_generation
  release="${release:A}"
  [[ -L "$PLAN_BUILD_CACHE_DIR/current" ]] || fail "$1 current is a symlink"
  [[ -d "$release" ]] || fail "$1 current resolves to a release"
  script_generation="$(command sed -n 's/^# generation: //p' "$release/plan_build.zsh")"
  skill_generation="$(command sed -n 's/ standard skill$//p' "$release/SKILL.md")"
  architect_generation="$(command sed -n 's/ architect skill$//p' "$release/ARCHITECT.md")"
  assert_equal "$script_generation" "$skill_generation" "$1 script/standard generation"
  assert_equal "$script_generation" "$architect_generation" "$1 script/architect generation"
}

assert_skill_links() {
  [[ -L "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" ]] || fail "$1 standard path is symlink"
  [[ -L "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md" ]] || fail "$1 architect path is symlink"
  assert_equal "$PLAN_BUILD_CACHE_DIR/current/SKILL.md" \
    "$(command readlink "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md")" "$1 standard link target"
  assert_equal "$PLAN_BUILD_CACHE_DIR/current/ARCHITECT.md" \
    "$(command readlink "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md")" "$1 architect link target"
}

write_bundle() {
  local generation="$1"
  local exit_code="${2:-0}"
  local marker_file="${3:-}"
  print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
  print -r -- "# generation: $generation" >> "$source_dir/plan_build.zsh"
  print -r -- "print -r -- \"$generation:\${(j:|:)@}\"" >> "$source_dir/plan_build.zsh"
  print -r -- "print -r -- \"path:\${0}\"" >> "$source_dir/plan_build.zsh"
  print -r -- 'print -r -- "skills:${PLAN_BUILD_SKILL_PATH}|${PLAN_BUILD_ARCHITECT_SKILL_PATH}"' >> "$source_dir/plan_build.zsh"
  [[ -n "$marker_file" ]] && print -r -- ": > ${(q)marker_file}" >> "$source_dir/plan_build.zsh"
  print -r -- "exit $exit_code" >> "$source_dir/plan_build.zsh"
  print -r -- "$generation standard skill" > "$source_dir/SKILL.md"
  print -r -- "$generation architect skill" > "$source_dir/ARCHITECT.md"
}

run_plan_build() {
  output="$(plan_build "$@" 2>&1)"
  rc=$?
}

write_bundle initial
run_plan_build alpha "two words"
assert_equal 0 "$rc" "initial migration status"
assert_contains "$output" "initial:alpha|two words" "argument forwarding"
assert_skill_links "initial migration"
assert_bundle_consistent "initial migration"
assert_file_contains "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" "initial standard" "initial standard content"
assert_file_contains "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md" "initial architect" "initial architect content"
execution_path="${${(f)output}[(r)path:*]#path:}"
[[ "$execution_path" == "$PLAN_BUILD_CACHE_DIR"/release.*/plan_build.zsh ]] ||
  fail "execution uses an immutable release path"
assert_contains "$output" "skills:${execution_path:h}/SKILL.md|${execution_path:h}/ARCHITECT.md" \
  "execution binds immutable skill paths"

old_release="$PLAN_BUILD_CACHE_DIR/current"
old_release="${old_release:A}"
write_bundle replacement 23
run_plan_build forwarded
assert_equal 23 "$rc" "canonical status forwarding"
assert_contains "$output" "replacement:forwarded" "replacement executes"
new_release="$PLAN_BUILD_CACHE_DIR/current"
new_release="${new_release:A}"
[[ "$new_release" != "$old_release" ]] || fail "refresh activates a unique release"
assert_skill_links "replacement"
assert_bundle_consistent "replacement"

print -r -- 'if broken' > "$source_dir/plan_build.zsh"
print -r -- "bad standard skill" > "$source_dir/SKILL.md"
print -r -- "bad architect skill" > "$source_dir/ARCHITECT.md"
run_plan_build fallback
assert_equal 23 "$rc" "failed refresh fallback status"
assert_contains "$output" "Warning: Unable to refresh" "failed refresh warning"
assert_contains "$output" "replacement:fallback" "failed refresh executes current"
preserved_release="$PLAN_BUILD_CACHE_DIR/current"
preserved_release="${preserved_release:A}"
assert_equal "$new_release" "$preserved_release" "failed refresh preserves current"
assert_skill_links "failed refresh"
assert_bundle_consistent "failed refresh"

export PLAN_BUILD_CURL_COMMAND="definitely-missing-plan-build-curl"
run_plan_build no-curl
assert_equal 23 "$rc" "missing curl fallback status"
assert_contains "$output" "curl is unavailable" "missing curl warning"
assert_contains "$output" "replacement:no-curl" "missing curl executes current"
rm -f "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md"
run_plan_build repaired-offline
assert_equal 23 "$rc" "offline skill repair status"
assert_skill_links "offline skill repair"
unset PLAN_BUILD_CURL_COMMAND

# An already selected immutable script must not change beneath a concurrent caller.
ready_file="$fixture_dir/ready"
go_file="$fixture_dir/go"
first_output="$fixture_dir/first.out"
print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
print -r -- '# generation: concurrent_first' >> "$source_dir/plan_build.zsh"
print -r -- 'print -r -- "concurrent_first:${(j:|:)@}"' >> "$source_dir/plan_build.zsh"
print -r -- ": > ${(q)ready_file}" >> "$source_dir/plan_build.zsh"
print -r -- "while [[ ! -e ${(q)go_file} ]]; do sleep 0.01; done" >> "$source_dir/plan_build.zsh"
print -r -- 'print -r -- "finished:first"' >> "$source_dir/plan_build.zsh"
print -r -- "concurrent_first standard skill" > "$source_dir/SKILL.md"
print -r -- "concurrent_first architect skill" > "$source_dir/ARCHITECT.md"
(plan_build one > "$first_output" 2>&1) &
first_pid=$!
for attempt in {1..500}; do
  [[ -e "$ready_file" ]] && break
  sleep 0.01
done
[[ -e "$ready_file" ]] || fail "first concurrent call reached canonical execution"
write_bundle concurrent_second
run_plan_build two
assert_equal 0 "$rc" "second concurrent status"
assert_contains "$output" "concurrent_second:two" "second concurrent generation"
: > "$go_file"
wait "$first_pid" || fail "first concurrent status"
first_text="$(<"$first_output")"
assert_contains "$first_text" "concurrent_first:one" "first call retains selected release"
assert_contains "$first_text" "finished:first" "first immutable script finishes"
assert_bundle_consistent "concurrent final current"
assert_skill_links "concurrent final links"

# Cancellation while refreshing must clean staging and never execute canonical code.
blocking_curl="$fixture_dir/blocking-curl"
cancel_started="$fixture_dir/cancel.started"
cancel_executed="$fixture_dir/cancel.executed"
write_bundle cancellation-marker 0 "$cancel_executed"
run_plan_build activate-cancellation-marker
assert_equal 0 "$rc" "cancellation marker bundle activation"
rm -f "$cancel_executed"
{
  print -r -- '#!/bin/sh'
  print -r -- "touch ${(q)cancel_started}"
  print -r -- 'while :; do sleep 1; done'
} > "$blocking_curl"
chmod +x "$blocking_curl"
export PLAN_BUILD_CURL_COMMAND="$blocking_curl"
(plan_build cancelled > "$fixture_dir/cancel.out" 2>&1; cancel_rc=$?; print -r -- "$cancel_rc" > "$fixture_dir/cancel.rc"; exit "$cancel_rc") &
cancel_pid=$!
for attempt in {1..500}; do
  [[ -e "$cancel_started" ]] && break
  sleep 0.01
done
kill -TERM "$cancel_pid"
wait "$cancel_pid"
cancel_wait_rc=$?
cancel_recorded_rc=""
[[ -s "$fixture_dir/cancel.rc" ]] && cancel_recorded_rc="$(<"$fixture_dir/cancel.rc")"
[[ "$cancel_wait_rc" = 143 || "$cancel_recorded_rc" = 143 ]] ||
  fail "TERM returns conventional status 143"
[[ ! -e "$cancel_executed" ]] || fail "TERM does not execute canonical plan-build"
unset PLAN_BUILD_CURL_COMMAND

# A pre-existing lock file must not be mistaken for an active kernel lock.
print -r -- "stale contents" > "$PLAN_BUILD_CACHE_DIR/.activate.lock"
write_bundle stale-lock-recovery
run_plan_build recovered
assert_equal 0 "$rc" "stale activation lock recovery status"
assert_contains "$output" "stale-lock-recovery:recovered" "stale activation lock recovery execution"

# Cancellation while canonical code runs must terminate the child promptly.
canonical_started="$fixture_dir/canonical.started"
canonical_finished="$fixture_dir/canonical.finished"
print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
print -r -- '# generation: canonical-cancel' >> "$source_dir/plan_build.zsh"
print -r -- "trap 'exit 143' TERM" >> "$source_dir/plan_build.zsh"
print -r -- ": > ${(q)canonical_started}" >> "$source_dir/plan_build.zsh"
print -r -- 'while :; do sleep 1; done' >> "$source_dir/plan_build.zsh"
print -r -- ": > ${(q)canonical_finished}" >> "$source_dir/plan_build.zsh"
print -r -- "canonical-cancel standard skill" > "$source_dir/SKILL.md"
print -r -- "canonical-cancel architect skill" > "$source_dir/ARCHITECT.md"
(plan_build canonical-cancel > "$fixture_dir/canonical-cancel.out" 2>&1; print -r -- "$?" > "$fixture_dir/canonical-cancel.rc") &
canonical_pid=$!
for attempt in {1..500}; do
  [[ -e "$canonical_started" ]] && break
  sleep 0.01
done
[[ -e "$canonical_started" ]] || fail "canonical cancellation reached execution"
kill -TERM "$canonical_pid"
wait "$canonical_pid"
assert_equal 143 "$(<"$fixture_dir/canonical-cancel.rc")" "canonical cancellation status"
[[ ! -e "$canonical_finished" ]] || fail "canonical child continued after TERM"

temp_count="$(command find "$PLAN_BUILD_CACHE_DIR" -maxdepth 1 \
  \( -name '.stage.*' -o -name '.current.*' \) -print | command wc -l | command tr -d ' ')"
assert_equal 0 "$temp_count" "cache staging and temporary links cleaned"
skill_temp_count="$(command find "$PLAN_BUILD_SKILLS_DIR" \
  \( -name '*.link.*' -o -name '*.backup.*' \) -print | command wc -l | command tr -d ' ')"
assert_equal 0 "$skill_temp_count" "skill temporary links and backups cleaned"

if (( failures )); then
  exit 1
fi

print -- "PASS: plan_build launcher tests"
