#!/usr/bin/env zsh

set -u

typeset -r repo_dir="${0:A:h}"
source "$repo_dir/plan_build.zsh"

typeset -gi failures=0
functions[_plan_build_real_launch_claude]="${functions[_plan_build_launch_claude]}"
functions[_plan_build_real_enhance_prompt]="${functions[_plan_build_enhance_prompt]}"
functions[_plan_build_real_start_auggie_script]="${functions[_plan_build_start_auggie_script]}"
functions[_plan_build_real_agentmemory_check]="${functions[_plan_build_agentmemory_check]}"

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

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

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

assert_not_contains() {
  local output="$1" unexpected="$2" label="$3"
  [[ "$output" != *"$unexpected"* ]] || fail "$label (unexpected '$unexpected')"
}

run_plan() {
  local args="$1" input="${2:-EOF\n}"
  output="$(printf '%b' "$input" | plan_build ${(z)args} 2>&1)"
  rc=$?
}

seam_events() {
  print -r -- "$1" | sed -n 's/^SEAM://p' | paste -sd ' ' -
}

count_matches() {
  local directory="$1" prefix="$2"
  local -a matches
  matches=("$directory"/"$prefix".*(N))
  print -r -- "$#matches"
}

typeset fixture_dir
fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan-build-tests.XXXXXX")" || exit 1
trap 'rm -rf -- "$fixture_dir"' EXIT
mkdir -p "$fixture_dir/tmp"
export TMPDIR="$fixture_dir/tmp"

command git init -q "$fixture_dir/worktree"
command git init -q --bare "$fixture_dir/bare.git"

(cd "$fixture_dir/worktree" && _plan_build_is_worktree)
assert_equal 0 "$?" "normal Git worktree accepted"
(cd "$fixture_dir/bare.git" && _plan_build_is_worktree)
assert_equal 1 "$?" "bare Git repository rejected"
(cd "$fixture_dir" && _plan_build_is_worktree)
assert_equal 1 "$?" "non-repository rejected"

plugin_state() {
  print -r -- "$1" | _plan_build_claude_plugin_state "$2"
}

assert_equal enabled "$(plugin_state $'  ❯ agentmemory@agentmemory\n    Version: 1\n    Status: ✔ enabled' 'agentmemory@agentmemory')" "enabled Claude plugin"
assert_equal disabled "$(plugin_state $'  ❯ mattpocock-skills@mattpocock\n    Status: disabled' 'mattpocock-skills@mattpocock')" "disabled Claude plugin"
assert_equal missing "$(plugin_state $'  ❯ other@market\n    Status: ✔ enabled' 'agentmemory@agentmemory')" "missing Claude plugin"
assert_equal installed "$(plugin_state '  ❯ agentmemory@agentmemory' 'agentmemory@agentmemory')" "Claude plugin without status"

assert_equal enabled "$(print -r -- 'agentmemory@agentmemory  installed, enabled  1.0  /tmp/plugin' | _plan_build_codex_plugin_state)" "enabled Codex plugin"
assert_equal disabled "$(print -r -- $'agentmemory@agentmemory  installed, disabled  1.0  /tmp/plugin\nother@market  installed, enabled  1.0  /tmp/other' | _plan_build_codex_plugin_state)" "disabled Codex plugin is not confused by later plugin"
assert_equal missing "$(print -r -- 'other@market  installed, enabled  1.0  /tmp/other' | _plan_build_codex_plugin_state)" "missing Codex plugin"

_plan_build_project_id_is_valid "github.com/example/project"
assert_equal 0 "$?" "valid project ID"
_plan_build_project_id_is_valid "invalid project id"
assert_equal 1 "$?" "project ID rejects spaces"
_plan_build_project_id_is_valid "x"
assert_equal 1 "$?" "project ID minimum length"

command git -C "$fixture_dir/worktree" remote add origin \
  "https://opengist.example/owner/project.git"
assert_equal "opengist.example/owner/project" \
  "$(_plan_build_default_project_id "$fixture_dir/worktree")" \
  "HTTPS remote normalizes to stable project ID"
command git -C "$fixture_dir/worktree" remote set-url origin \
  "git@opengist.example:owner/project.git"
assert_equal "opengist.example/owner/project" \
  "$(_plan_build_default_project_id "$fixture_dir/worktree")" \
  "SSH remote normalizes to stable project ID"
command git -C "$fixture_dir/worktree" remote set-url origin \
  "https://opengist.example:8443/owner/project.git"
assert_equal "opengist.example:8443/owner/project" \
  "$(_plan_build_default_project_id "$fixture_dir/worktree")" \
  "HTTPS remote preserves port in stable project ID"

print -r -- "github.com/example/project" >| "$fixture_dir/worktree/.agentmemory-project"
command git -C "$fixture_dir/worktree" add .agentmemory-project
command git -C "$fixture_dir/worktree" -c user.name=Test -c user.email=test@example.invalid \
  commit -qm "test: add project identity"
project_id_output="$(cd "$fixture_dir/worktree" && _plan_build_resolve_project_id && print -r -- "$REPLY")"
assert_equal "github.com/example/project" "$project_id_output" "committed project ID resolves"
print -r -- "invalid project id" >| "$fixture_dir/worktree/.agentmemory-project"
(cd "$fixture_dir/worktree" && _plan_build_resolve_project_id) >/dev/null 2>&1
assert_equal 1 "$?" "invalid committed project ID rejected"
print -r -- "github.com/example/other" >| "$fixture_dir/worktree/.agentmemory-project"
(cd "$fixture_dir/worktree" && _plan_build_resolve_project_id) >/dev/null 2>&1
assert_equal 1 "$?" "changed project ID rejected until committed"
rm -f -- "$fixture_dir/worktree/.agentmemory-project"
_plan_build_has_tty() { return 1 }
(cd "$fixture_dir/worktree" && _plan_build_resolve_project_id) >/dev/null 2>&1
assert_equal 1 "$?" "missing project ID fails without terminal"
unfunction _plan_build_has_tty

typeset args expected label
for args expected label in \
  "--v2" "Unknown argument: --v2" "v2 rejection" \
  "--unknown" "Unknown argument: --unknown" "unknown option" \
  "--yolo --yolo" "Duplicate argument: --yolo" "duplicate yolo" \
  "--prompt --prompt" "Duplicate argument: --prompt" "duplicate prompt" \
  "--brainstorm" "Unknown argument: --brainstorm" "removed brainstorm" \
  "--writing-plan" "Unknown argument: --writing-plan" "removed writing plan" \
  "--architect --architect" "Duplicate argument: --architect" "duplicate architect" \
  "--architect --new --new" "Duplicate argument: --new" "duplicate new" \
  "--new" "--new requires --architect" "orphan new" \
  "--architect --prompt" "may combine only with --new and --yolo" "architect prompt conflict"; do
  run_plan "$args"
  assert_equal 1 "$rc" "$label status"
  assert_contains "$output" "$expected" "$label message"
done

export CLAUDE_CODE_SAFE_MODE=1
output="$(_plan_build_safe_mode_preflight 2>&1)"
rc=$?
unset CLAUDE_CODE_SAFE_MODE
assert_equal 1 "$rc" "safe mode failure"
assert_contains "$output" "safe mode disables the Matt Pocock and AgentMemory plugins" "safe mode guidance"

_plan_build_memory_action_from_reply retry
assert_equal 2 "$?" "memory action parses retry"
_plan_build_memory_action_from_reply continue
assert_equal 0 "$?" "memory action parses continue"
_plan_build_memory_action_from_reply stop
assert_equal 1 "$?" "memory action parses stop"

_plan_build_agentmemory_health() { return 0 }
_plan_build_claude_agentmemory_state() { print -r -- enabled }
_plan_build_codex_agentmemory_state() { print -r -- enabled }
_plan_build_real_agentmemory_check >/dev/null
assert_equal 0 "$?" "AgentMemory check accepts healthy shared setup"
_plan_build_codex_agentmemory_state() { print -r -- disabled }
_plan_build_real_agentmemory_check >/dev/null
assert_equal 1 "$?" "AgentMemory check rejects disabled Codex plugin"

typeset -gi memory_check_attempts=0
_plan_build_agentmemory_check() {
  memory_check_attempts=$((memory_check_attempts + 1))
  (( memory_check_attempts >= 2 ))
}
_plan_build_confirm_memory_action() { return 2 }
_plan_build_agentmemory_preflight >/dev/null
assert_equal 0 "$?" "memory retry can recover"
assert_equal 2 "$memory_check_attempts" "memory retry repeats health check"
assert_equal required "$REPLY" "recovered memory remains required"

_plan_build_agentmemory_check() { return 1 }
_plan_build_confirm_memory_action() { return 0 }
_plan_build_agentmemory_preflight >/dev/null
assert_equal 0 "$?" "approved degraded memory status"
assert_equal degraded "$REPLY" "approved degraded memory mode"
_plan_build_confirm_memory_action() { return 1 }
_plan_build_agentmemory_preflight >/dev/null
assert_equal 1 "$?" "rejected degraded memory status"
unfunction _plan_build_agentmemory_check _plan_build_confirm_memory_action

mkdir -p "$fixture_dir/bin" "$fixture_dir/home/.augment" "$fixture_dir/enhance-tmp"
print -r -- '{}' >| "$fixture_dir/home/.augment/session.json"
print -r -- '#!/bin/sh' >| "$fixture_dir/bin/script"
print -r -- 'printf "%s\n" "$@" > "$PLAN_BUILD_SCRIPT_ARGS"' >> "$fixture_dir/bin/script"
print -r -- 'if [ "$1" = -q ] && [ "$2" = -t ]; then log_file=$4; shift 4; "$@" > "$log_file"; exit $?; fi' >> "$fixture_dir/bin/script"
print -r -- 'log_file=$5; printf "Enhanced prompt: linux result\n" > "$log_file"; exit "${PLAN_BUILD_SCRIPT_STATUS:-0}"' >> "$fixture_dir/bin/script"
print -r -- '#!/bin/sh' >| "$fixture_dir/bin/auggie"
print -r -- 'printf "Enhanced prompt: mac result\n"; exit "${PLAN_BUILD_AUGGIE_STATUS:-0}"' >> "$fixture_dir/bin/auggie"
chmod +x "$fixture_dir/bin/script" "$fixture_dir/bin/auggie"

_plan_build_confirm_indexing() { return 1 }

typeset platform script_args enhance_output
trap 'print -u2 -r -- "test INT trap"' INT
typeset caller_int_trap="$(trap -p INT)"
for platform in Darwin Linux; do
  _plan_build_platform() { print -r -- "$platform" }
  : >| "$fixture_dir/script-args"
  enhance_output="$fixture_dir/enhanced-$platform"
  HOME="$fixture_dir/home" TMPDIR="$fixture_dir/enhance-tmp" \
    PATH="$fixture_dir/bin:$PATH" PLAN_BUILD_SCRIPT_ARGS="$fixture_dir/script-args" \
    _plan_build_real_enhance_prompt "raw prompt" "$enhance_output"
  rc=$?
  assert_equal 0 "$rc" "$platform script enhancement status"
  script_args="$(<"$fixture_dir/script-args")"
  if [[ "$platform" == Darwin ]]; then
    assert_contains "$script_args" $'-t\n0' "macOS script immediate flushing"
    assert_contains "$script_args" $'auggie\n--print' "macOS script direct executable dispatch"
    assert_not_contains "$script_args" $'\ncommand\n' "macOS script excludes shell builtin"
    assert_equal "mac result" "$(<"$enhance_output")" "macOS parsed enhancement"
  else
    assert_contains "$script_args" $'-q\n-f' "Linux script immediate flushing"
    assert_contains "$script_args" $'-e\n-O' "Linux script propagates child status"
    assert_contains "$script_args" "-c" "Linux script command mode"
    assert_equal "linux result" "$(<"$enhance_output")" "Linux parsed enhancement"
  fi
  assert_equal 0 "$(count_matches "$fixture_dir/enhance-tmp" plan-build-auggie)" "$platform run directory cleanup"
done
assert_equal "$caller_int_trap" "$(trap -p INT)" "enhancement traps remain local to caller"
trap - INT

platform=Darwin
_plan_build_platform() { print -r -- "$platform" }
PLAN_BUILD_AUGGIE_STATUS=7 HOME="$fixture_dir/home" TMPDIR="$fixture_dir/enhance-tmp" \
  PATH="$fixture_dir/bin:$PATH" PLAN_BUILD_SCRIPT_ARGS="$fixture_dir/script-args" \
  _plan_build_real_enhance_prompt "partial prompt" "$fixture_dir/failed-enhancement" >/dev/null 2>&1
rc=$?
assert_equal 7 "$rc" "nonzero Auggie status propagated"
[[ ! -e "$fixture_dir/failed-enhancement" ]] || fail "failed Auggie output was accepted"
assert_equal 0 "$(count_matches "$fixture_dir/enhance-tmp" plan-build-auggie)" "failed run directory cleanup"

platform=Linux
PLAN_BUILD_SCRIPT_STATUS=7 HOME="$fixture_dir/home" TMPDIR="$fixture_dir/enhance-tmp" \
  PATH="$fixture_dir/bin:$PATH" PLAN_BUILD_SCRIPT_ARGS="$fixture_dir/script-args" \
  _plan_build_real_enhance_prompt "partial prompt" "$fixture_dir/failed-linux-enhancement" >/dev/null 2>&1
rc=$?
assert_equal 7 "$rc" "Linux script child status propagated"
[[ ! -e "$fixture_dir/failed-linux-enhancement" ]] || fail "failed Linux Auggie output was accepted"

print -r -- $'noise\r\n\e[32m✨ Enhanced prompt: first\e[0m\r\nsecond\r\n🤖 tool' >| "$fixture_dir/parser.log"
_plan_build_parse_auggie_output "$fixture_dir/parser.log" "$fixture_dir/parser.out"
assert_equal $'first\nsecond' "$(<"$fixture_dir/parser.out")" "parser strips terminal output and stops at marker"
print -r -- "no enhanced prompt" >| "$fixture_dir/parser-empty.log"
_plan_build_parse_auggie_output "$fixture_dir/parser-empty.log" "$fixture_dir/parser-empty.out"
[[ ! -s "$fixture_dir/parser-empty.out" ]] || fail "parser accepted output without marker"

typeset -g enhanced_text="enhanced payload"
typeset -gi enhanced_approval=0
typeset -gi common_preflight_result=0
typeset -gi enhancement_result=0

_plan_build_require_cli() {
  print -r -- "SEAM:cli:$1"
  return 0
}
_plan_build_common_preflight() {
  print -r -- "SEAM:common:$2"
  return "$common_preflight_result"
}
_plan_build_resolve_project_id() {
  print -r -- "SEAM:project-id"
  REPLY="github.com/example/project"
}
_plan_build_agentmemory_preflight() {
  print -r -- "SEAM:memory"
  REPLY="required"
}
_plan_build_enhance_prompt() {
  print -r -- "SEAM:auggie:$1"
  print -r -- "$enhanced_text" >| "$2"
  return "$enhancement_result"
}
_plan_build_confirm_enhanced_prompt() {
  print -r -- "SEAM:enhanced-approval"
  return "$enhanced_approval"
}
_plan_build_launch_claude() {
  print -r -- "SEAM:claude"
  printf 'CLAUDE_YOLO=<%s>\nCLAUDE_PROJECT=<%s>\nCLAUDE_MEMORY=<%s>\nCLAUDE_PROMPT=<%s>\n' "$1" "$2" "$3" "$4"
}

run_plan "" "first line\nsecond line\nEOF\nignored line\n"
assert_equal 0 "$rc" "Matt default mode status"
assert_contains "$output" "memory-aware Matt Pocock workflow" "Matt workflow prompt"
assert_contains "$output" "AgentMemory project: github.com/example/project" "memory project prompt"
assert_contains "$output" "Run the mandatory recall gate" "recall gate prompt"
assert_contains "$output" $'first line\nsecond line' "multiline payload"
assert_not_contains "$output" "ignored line" "EOF terminates payload"
assert_equal "common:1 project-id memory claude" "$(seam_events "$output")" "default preflight ordering"

enhanced_approval=0
run_plan "--prompt" "raw payload\nEOF\n"
assert_equal 0 "$rc" "prompt mode approved status"
assert_contains "$output" "enhanced payload" "enhanced prompt used"
assert_contains "$output" "Enhanced prompt approved" "enhanced approval reported"
assert_equal "common:1 cli:auggie cli:script cli:perl project-id memory auggie:raw payload enhanced-approval claude" "$(seam_events "$output")" "prompt seams ordering"

enhancement_result=9
run_plan "--prompt" "failed child\nEOF\n"
assert_equal 9 "$rc" "prompt child failure status propagated"
assert_not_contains "$output" "enhanced-approval" "failed enhancement is never parsed or approved"
assert_not_contains "$output" "SEAM:claude" "failed enhancement prevents launch"
assert_equal 0 "$(count_matches "$TMPDIR" plan-build-enhanced)" "enhanced temporary file cleanup"
enhancement_result=0

enhanced_approval=1
run_plan "--prompt" "declined\nEOF\n"
assert_equal 0 "$rc" "prompt rejection exits cleanly"
assert_contains "$output" "not approved" "prompt rejection message"
assert_equal "common:1 cli:auggie cli:script cli:perl project-id memory auggie:declined enhanced-approval" "$(seam_events "$output")" "prompt rejection prevents launch"
enhanced_approval=0

run_plan "--architect" "design feature\nEOF\n"
assert_equal 0 "$rc" "architect safe-resume status"
assert_contains "$output" "Start mode: safe-resume-detection" "architect safe-resume mode"
assert_contains "$output" "documentation-first architect/orchestrator role" "architect role boundary"
assert_contains "$output" "Run the skill's mandatory recall gate" "architect recall gate prompt"
assert_equal "common:0 project-id memory claude" "$(seam_events "$output")" "architect preflight before launch"

run_plan "--architect --new" "fresh design\nEOF\n"
assert_equal 0 "$rc" "architect new status"
assert_contains "$output" "Start mode: archive-and-start-new" "architect new mode"

run_plan "--architect --yolo" "fast design\nEOF\n"
assert_equal 0 "$rc" "architect yolo status"
assert_contains "$output" "CLAUDE_YOLO=<1>" "architect yolo reaches launch seam"
assert_contains "$output" "including every gate" "architect yolo retains gates"

common_preflight_result=1
run_plan "--architect" "must not be read\nEOF\n"
assert_equal 1 "$rc" "architect preflight failure status"
assert_not_contains "$output" "Reading payload" "architect preflight precedes payload"
assert_equal "common:0" "$(seam_events "$output")" "architect failure stops processing"
common_preflight_result=0

run_plan "" "EOF\n"
assert_equal 1 "$rc" "empty payload status"
assert_contains "$output" "Payload was empty" "empty payload message"

print -r -- '#!/bin/sh' >| "$fixture_dir/bin/claude"
print -r -- 'printf "PROJECT=<%s> MEMORY=<%s> INJECT=<%s> REAL_CLAUDE:" "$AGENTMEMORY_PROJECT_NAME" "$PLAN_BUILD_MEMORY_MODE" "$AGENTMEMORY_INJECT_CONTEXT"; printf " <%s>" "$@"; printf "\n"' >> "$fixture_dir/bin/claude"
chmod +x "$fixture_dir/bin/claude"
output="$(PATH="$fixture_dir/bin:$PATH" _plan_build_real_launch_claude 1 "github.com/example/project" required "payload")"
assert_contains "$output" "<--dangerously-skip-permissions>" "yolo translates to real Claude argument"
assert_not_contains "$output" "<--yolo>" "yolo alias not forwarded"
assert_contains "$output" "PROJECT=<github.com/example/project>" "project ID reaches Claude environment"
assert_contains "$output" "MEMORY=<required>" "memory mode reaches Claude environment"
assert_contains "$output" "INJECT=<true>" "context injection reaches Claude environment"
output="$(PATH="$fixture_dir/bin:$PATH" _plan_build_real_launch_claude 0 "github.com/example/project" degraded "payload")"
assert_contains "$output" "MEMORY=<degraded>" "degraded memory mode reaches Claude environment"
assert_contains "$output" "INJECT=<false>" "degraded mode disables context injection"

if (( failures )); then
  print -u2 -r -- "FAIL: $failures plan_build test(s)"
  exit 1
fi

print -r -- "PASS: plan_build tests"
