#!/usr/bin/env bash
set -uo pipefail
export LC_ALL=C

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CLI="$ROOT/bin/archive-worktrees"
PASS=0
FAIL=0
TMP="$(mktemp -d "${TMPDIR:-/tmp}/archive-worktrees-test.XXXXXX")"
trap 'rm -rf "$TMP"' EXIT

ok() { PASS=$((PASS + 1)); printf 'PASS %s\n' "$1"; }
bad() { FAIL=$((FAIL + 1)); printf 'FAIL %s\n     %s\n' "$1" "$2"; }
expect_status() {
  local want="$1" name="$2"; shift 2
  local out rc
  out="$($@ 2>&1)"; rc=$?
  if [[ "$rc" -eq "$want" ]]; then ok "$name"; else bad "$name" "expected exit $want, got $rc: $out"; fi
  printf '%s' "$out"
}
json_field() {
  local field="$1" json="$2"
  python3 - "$field" "$json" <<'PY'
import json
import sys
obj = json.loads(sys.argv[2])
value = obj
for part in sys.argv[1].split('.'):
    value = value[part]
if isinstance(value, (dict, list)):
    print(json.dumps(value, sort_keys=True))
else:
    print(value)
PY
}

new_repo() {
  local name="$1"
  local repo="$TMP/$name" remote="$TMP/$name-remote.git"
  mkdir -p "$repo"
  git init -q --bare "$remote"
  git init -q "$repo"
  git -C "$repo" config user.name Test
  git -C "$repo" config user.email test@example.invalid
  git -C "$repo" config commit.gpgsign false
  mkdir -p "$repo/src"
  printf 'base\n' > "$repo/src/app.py"
  printf 'base readme\n' > "$repo/README.md"
  printf 'ignored\n' > "$repo/.gitignore"
  {
    printf '.cache/\n'
    printf '*.log\n'
    printf '.env\n'
    printf '*.mystery\n'
  } > "$repo/.gitignore"
  git -C "$repo" add -A
  git -C "$repo" commit -qm initial
  git -C "$repo" branch -M main
  git -C "$repo" remote add origin "$remote"
  git -C "$repo" push -q -u origin main
  git -C "$repo" remote set-head origin -a >/dev/null 2>&1 || true
  mkdir -p "$repo/.worktrees"
  printf '%s\n' "$repo"
}
new_worktree() {
  local repo="$1" name="$2"
  git -C "$repo" worktree add -q -b "wt/$name" "$repo/.worktrees/$name" main
  printf '%s\n' "$repo/.worktrees/$name"
}

REPO="$(new_repo base)"

printf '%s\n' '== CLI presence and audit inventory =='
if [[ -x "$CLI" ]]; then ok 'archive-worktrees is executable'; else bad 'archive-worktrees is executable' 'missing executable'; fi
WT_AUDIT="$(new_worktree "$REPO" audit)"
printf 'staged\n' >> "$WT_AUDIT/src/app.py"
printf 'unstaged\n' >> "$WT_AUDIT/README.md"
git -C "$WT_AUDIT" add src/app.py
printf 'new source\n' > "$WT_AUDIT/src/new.ts"
mkdir -p "$WT_AUDIT/.cache"
printf 'generated\n' > "$WT_AUDIT/.cache/output"
printf 'secret-value-should-never-appear\n' > "$WT_AUDIT/.env"
AUDIT_OUT="$("$CLI" audit "$WT_AUDIT" 2>&1)"; AUDIT_RC=$?
if [[ "$AUDIT_RC" -eq 0 && "$AUDIT_OUT" == *'"staged": 1'* && "$AUDIT_OUT" == *'"unstaged": 1'* && "$AUDIT_OUT" == *'"untracked": 1'* && "$AUDIT_OUT" == *'"ignored": 2'* ]]; then
  ok 'audit inventories staged, unstaged, untracked, ignored paths'
else
  bad 'audit inventories staged, unstaged, untracked, ignored paths' "rc=$AUDIT_RC output=$AUDIT_OUT"
fi
if [[ "$AUDIT_OUT" != *'secret-value-should-never-appear'* ]]; then ok 'audit never prints secret contents'; else bad 'audit never prints secret contents' "$AUDIT_OUT"; fi

WT_RENAME="$(new_worktree "$REPO" rename)"
git -C "$WT_RENAME" mv src/app.py src/renamed.py
RENAME_OUT="$($CLI audit "$WT_RENAME" 2>&1)"; RENAME_RC=$?
if [[ "$RENAME_RC" -eq 0 && "$RENAME_OUT" == *'src/renamed.py'* && "$RENAME_OUT" != *'src/app.py'* ]]; then ok 'audit parses NUL-delimited rename records'; else bad 'audit parses NUL-delimited rename records' "rc=$RENAME_RC output=$RENAME_OUT"; fi

printf '%s\n' '== preserve success and restore =='
WT_GOOD="$(new_worktree "$REPO" good)"
printf 'staged-change\n' >> "$WT_GOOD/src/app.py"
git -C "$WT_GOOD" add src/app.py
printf 'unstaged-change\n' >> "$WT_GOOD/src/app.py"
printf 'new source\n' > "$WT_GOOD/src/new.ts"
ORIGINAL_INDEX_TREE="$(git -C "$WT_GOOD" write-tree)"
mkdir -p "$WT_GOOD/.cache"
printf 'ignored cache\n' > "$WT_GOOD/.cache/output"
printf 'ignored log\n' > "$WT_GOOD/run.log"
PRESERVE_OUT="$("$CLI" preserve "$WT_GOOD" 2>&1)"; PRESERVE_RC=$?
if [[ "$PRESERVE_RC" -eq 0 ]]; then ok 'preserve succeeds for safe source WIP'; else bad 'preserve succeeds for safe source WIP' "rc=$PRESERVE_RC output=$PRESERVE_OUT"; fi
PRESERVE_JSON="$(printf '%s\n' "$PRESERVE_OUT" | tail -n 1)"
ARCHIVE_BRANCH="$(json_field archive_branch "$PRESERVE_JSON" 2>/dev/null || true)"
SNAPSHOT_SHA="$(json_field snapshot_sha "$PRESERVE_JSON" 2>/dev/null || true)"
MANIFEST="$(json_field manifest_path "$PRESERVE_JSON" 2>/dev/null || true)"
STAGED_META="$(json_field staged_metadata_path "$PRESERVE_JSON" 2>/dev/null || true)"
if [[ -n "$ARCHIVE_BRANCH" && "$ARCHIVE_BRANCH" == archive/* && "$(git -C "$REPO" rev-parse "refs/heads/$ARCHIVE_BRANCH" 2>/dev/null)" == "$SNAPSHOT_SHA" ]]; then
  ok 'preserve creates dedicated archival branch at snapshot'; else bad 'preserve creates dedicated archival branch at snapshot' "branch=$ARCHIVE_BRANCH sha=$SNAPSHOT_SHA"; fi
REMOTE_SHA="$(git -C "$REPO" ls-remote --refs origin "refs/heads/$ARCHIVE_BRANCH" | awk '{print $1}')"
if [[ "$REMOTE_SHA" == "$SNAPSHOT_SHA" ]]; then ok 'preserve verifies exact remote snapshot SHA'; else bad 'preserve verifies exact remote snapshot SHA' "remote=$REMOTE_SHA snapshot=$SNAPSHOT_SHA"; fi
if [[ -f "$MANIFEST" && -f "$STAGED_META" ]]; then ok 'preserve writes recovery manifest and separate staged metadata'; else bad 'preserve writes recovery manifest and separate staged metadata' "manifest=$MANIFEST staged=$STAGED_META"; fi
SNAPSHOT_HAS_CACHE=0
git -C "$REPO" cat-file -e "$SNAPSHOT_SHA:.cache/output" 2>/dev/null && SNAPSHOT_HAS_CACHE=1
if [[ "$(git -C "$REPO" show "$SNAPSHOT_SHA:src/new.ts" 2>/dev/null)" == 'new source' && "$(git -C "$REPO" show "$SNAPSHOT_SHA:src/app.py" 2>/dev/null)" == *'unstaged-change'* && "$SNAPSHOT_HAS_CACHE" -eq 0 ]]; then
  ok 'snapshot contains safe source WIP and excludes cache output'; else bad 'snapshot contains safe source WIP and excludes cache output' 'snapshot tree mismatch'; fi
if [[ "$(python3 - "$STAGED_META" <<'PY'
import json, sys
obj=json.load(open(sys.argv[1]))
print(len(obj.get('staged_entries', [])))
PY
)" -ge 1 ]]; then ok 'staged-state metadata records staged paths separately'; else bad 'staged-state metadata records staged paths separately' "metadata=$STAGED_META"; fi

RESTORE="$REPO/.worktrees/restored"
RESTORE_OUT="$(cd "$REPO" && "$CLI" restore "$ARCHIVE_BRANCH" "$RESTORE" 2>&1)"; RESTORE_RC=$?
if [[ "$RESTORE_RC" -eq 0 && "$(git -C "$RESTORE" rev-parse HEAD 2>/dev/null)" == "$SNAPSHOT_SHA" ]]; then ok 'restore recreates worktree from archive branch'; else bad 'restore recreates worktree from archive branch' "rc=$RESTORE_RC output=$RESTORE_OUT"; fi
if [[ "$(git -C "$RESTORE" write-tree 2>/dev/null)" == "$ORIGINAL_INDEX_TREE" && "$(git -C "$RESTORE" status --porcelain=v1 2>/dev/null)" == *'MM src/app.py'* ]]; then
  ok 'restore reconstructs original staged and unstaged index boundary'
else
  bad 'restore reconstructs original staged and unstaged index boundary' "index=$(git -C "$RESTORE" write-tree 2>/dev/null) status=$(git -C "$RESTORE" status --porcelain=v1 2>/dev/null) expected=$ORIGINAL_INDEX_TREE"
fi

printf '%s\n' '== retire success and dangerous refusals =='
WT_CLEAN="$(new_worktree "$REPO" clean)"
CLEAN_PRESERVE="$("$CLI" preserve "$WT_CLEAN" 2>&1)"; CLEAN_RC=$?
CLEAN_JSON="$(printf '%s\n' "$CLEAN_PRESERVE" | tail -n 1)"
CLEAN_BRANCH="$(json_field archive_branch "$CLEAN_JSON" 2>/dev/null || true)"
if [[ "$CLEAN_RC" -eq 0 ]]; then ok 'preserve creates receipt for clean worktree'; else bad 'preserve creates receipt for clean worktree' "rc=$CLEAN_RC output=$CLEAN_PRESERVE"; fi
ALTERNATE_REMOTE="$TMP/alternate-remote.git"
git clone -q --bare "$REPO-remote.git" "$ALTERNATE_REMOTE"
git -C "$REPO" remote set-url origin "$ALTERNATE_REMOTE"
SUBSTITUTED_OUT="$($CLI retire "$WT_CLEAN" 2>&1)"; SUBSTITUTED_RC=$?
if [[ "$SUBSTITUTED_RC" -ne 0 && -e "$WT_CLEAN" && "$SUBSTITUTED_OUT" == *'origin changed'* ]]; then ok 'retire refuses origin substitution even with matching archive SHA'; else bad 'retire refuses origin substitution even with matching archive SHA' "rc=$SUBSTITUTED_RC output=$SUBSTITUTED_OUT"; fi
git -C "$REPO" remote set-url origin "$REPO-remote.git"
RETIRE_OUT="$($CLI retire "$WT_CLEAN" 2>&1)"; RETIRE_RC=$?
if [[ "$RETIRE_RC" -eq 0 && ! -e "$WT_CLEAN" && -d "$REPO/.git/worktree-archives" && "$(git -C "$REPO" rev-parse "refs/heads/$CLEAN_BRANCH" 2>/dev/null)" ]]; then
  ok 'retire removes only receipt-verified worktree without force'; else bad 'retire removes only receipt-verified worktree without force' "rc=$RETIRE_RC output=$RETIRE_OUT"; fi
if git -C "$REPO" show-ref --verify --quiet "refs/heads/wt/clean"; then ok 'retire keeps original branch'; else bad 'retire keeps original branch' 'original branch missing'; fi

WT_DEFAULT_OUT="$("$CLI" preserve "$REPO" 2>&1)"; WT_DEFAULT_RC=$?
if [[ "$WT_DEFAULT_RC" -ne 0 && "$WT_DEFAULT_OUT" == *'shared/default'* ]]; then ok 'preserve refuses shared default checkout'; else bad 'preserve refuses shared default checkout' "rc=$WT_DEFAULT_RC output=$WT_DEFAULT_OUT"; fi

WT_ACTIVE="$(new_worktree "$REPO" active)"
(cd "$WT_ACTIVE" && exec sleep 30) & ACTIVE_PID=$!
sleep 0.2
ACTIVE_OUT="$("$CLI" preserve "$WT_ACTIVE" 2>&1)"; ACTIVE_RC=$?
kill "$ACTIVE_PID" 2>/dev/null || true; wait "$ACTIVE_PID" 2>/dev/null || true
if [[ "$ACTIVE_RC" -ne 0 && "$ACTIVE_OUT" == *'active process'* ]]; then ok 'preserve refuses worktree with active process'; else bad 'preserve refuses worktree with active process' "rc=$ACTIVE_RC output=$ACTIVE_OUT"; fi

WT_SYMLINK="$(new_worktree "$REPO" symlink)"
ln -s /etc/passwd "$WT_SYMLINK/unsafe.ts"
SYMLINK_OUT="$("$CLI" preserve "$WT_SYMLINK" 2>&1)"; SYMLINK_RC=$?
if [[ "$SYMLINK_RC" -ne 0 && "$SYMLINK_OUT" == *'symlink'* ]]; then ok 'preserve refuses symlinked untracked path'; else bad 'preserve refuses symlinked untracked path' "rc=$SYMLINK_RC output=$SYMLINK_OUT"; fi

WT_UNCERTAIN="$(new_worktree "$REPO" uncertain)"
printf 'binary?\n' > "$WT_UNCERTAIN/mystery.bin"
UNCERTAIN_OUT="$("$CLI" preserve "$WT_UNCERTAIN" 2>&1)"; UNCERTAIN_RC=$?
if [[ "$UNCERTAIN_RC" -ne 0 && "$UNCERTAIN_OUT" == *'uncertain'* ]]; then ok 'preserve stops on uncertain untracked path'; else bad 'preserve stops on uncertain untracked path' "rc=$UNCERTAIN_RC output=$UNCERTAIN_OUT"; fi

WT_IGNORED="$(new_worktree "$REPO" ignored)"
printf 'ignored unknown\n' > "$WT_IGNORED/unknown.mystery"
IGNORED_OUT="$("$CLI" preserve "$WT_IGNORED" 2>&1)"; IGNORED_RC=$?
if [[ "$IGNORED_RC" -ne 0 && "$IGNORED_OUT" == *'uncertain ignored'* ]]; then ok 'preserve stops on uncertain ignored path'; else bad 'preserve stops on uncertain ignored path' "rc=$IGNORED_RC output=$IGNORED_OUT"; fi

WT_SECRET="$(new_worktree "$REPO" secret)"
printf 'TOP_SECRET=do-not-print\n' > "$WT_SECRET/.env"
SECRET_OUT="$("$CLI" preserve "$WT_SECRET" 2>&1)"; SECRET_RC=$?
if [[ "$SECRET_RC" -ne 0 && "$SECRET_OUT" == *'secret path'* && "$SECRET_OUT" != *'TOP_SECRET=do-not-print'* ]]; then ok 'preserve refuses secret path without exposing contents'; else bad 'preserve refuses secret path without exposing contents' "rc=$SECRET_RC output=$SECRET_OUT"; fi

TRAVERSAL_OUT="$("$CLI" preserve "$REPO/.worktrees/../.worktrees/$([[ -d "$WT_UNCERTAIN" ]] && basename "$WT_UNCERTAIN")" 2>&1)"; TRAVERSAL_RC=$?
if [[ "$TRAVERSAL_RC" -ne 0 && "$TRAVERSAL_OUT" == *'path traversal'* ]]; then ok 'preserve refuses traversal target'; else bad 'preserve refuses traversal target' "rc=$TRAVERSAL_RC output=$TRAVERSAL_OUT"; fi

printf '%s\n' '== remote mismatch, stale receipt, and partial multi-worktree failure =='
WT_REMOTE="$(new_worktree "$REPO" remote-mismatch)"
printf 'remote mismatch\n' > "$WT_REMOTE/src/remote.py"
FAKE_GIT="$TMP/fake-git"
cat > "$FAKE_GIT" <<'SH'
#!/usr/bin/env bash
args=("$@")
for ((i=0; i<${#args[@]}; i++)); do
  if [[ "${args[$i]}" == ls-remote ]]; then
    /usr/bin/git "${args[@]}" | sed 's/^[0-9a-f][0-9a-f]*/0000000000000000000000000000000000000000/'
    exit "${PIPESTATUS[0]}"
  fi
done
exec /usr/bin/git "${args[@]}"
SH
chmod +x "$FAKE_GIT"
FAKE_BIN="$TMP/fake-bin"
mkdir "$FAKE_BIN"
ln -s "$FAKE_GIT" "$FAKE_BIN/git"
REMOTE_OUT="$(PATH="$FAKE_BIN:$PATH" "$CLI" preserve "$WT_REMOTE" 2>&1)"; REMOTE_RC=$?
if [[ "$REMOTE_RC" -ne 0 && "$REMOTE_OUT" == *'remote SHA mismatch'* && ! -e "$REPO/.git/worktree-archives"/*remote-mismatch*.json ]]; then ok 'preserve refuses remote SHA mismatch'; else bad 'preserve refuses remote SHA mismatch' "rc=$REMOTE_RC output=$REMOTE_OUT"; fi

WT_STALE="$(new_worktree "$REPO" stale)"
STALE_PRESERVE="$("$CLI" preserve "$WT_STALE" 2>&1)"; STALE_JSON="$(printf '%s\n' "$STALE_PRESERVE" | tail -n 1)"
STALE_MANIFEST="$(json_field manifest_path "$STALE_JSON" 2>/dev/null || true)"
python3 - "$STALE_MANIFEST" <<'PY'
import json, sys
path=sys.argv[1]
data=json.load(open(path))
data['snapshot_sha']='0' * 40
with open(path, 'w') as fh: json.dump(data, fh, sort_keys=True)
PY
STALE_OUT="$("$CLI" retire "$WT_STALE" 2>&1)"; STALE_RC=$?
if [[ "$STALE_RC" -ne 0 && -e "$WT_STALE" && "$STALE_OUT" == *'receipt'* ]]; then ok 'retire refuses stale manifest'; else bad 'retire refuses stale manifest' "rc=$STALE_RC output=$STALE_OUT"; fi

WT_PART_GOOD="$(new_worktree "$REPO" partial-good)"
printf 'partial good\n' > "$WT_PART_GOOD/src/good.py"
WT_PART_BAD="$(new_worktree "$REPO" partial-bad)"
printf 'bad unknown\n' > "$WT_PART_BAD/unknown.bin"
PART_OUT="$("$CLI" preserve "$WT_PART_GOOD" "$WT_PART_BAD" 2>&1)"; PART_RC=$?
if [[ "$PART_RC" -ne 0 && "$PART_OUT" == *'partial-good'* && "$PART_OUT" == *'uncertain'* ]]; then
  PART_JSON="$(printf '%s\n' "$PART_OUT" | grep 'partial-good' | tail -n 1)"
  if [[ "$(json_field archive_branch "$PART_JSON" 2>/dev/null || true)" == archive/* ]]; then ok 'partial preserve processes good worktree despite bad worktree'; else bad 'partial preserve processes good worktree despite bad worktree' "$PART_OUT"; fi
else
  bad 'partial preserve processes good worktree despite bad worktree' "rc=$PART_RC output=$PART_OUT"
fi

WT_RETRY="$(new_worktree "$REPO" retry)"
printf 'retry source\n' > "$WT_RETRY/src/retry.py"
RETRY_FAKE_GIT="$TMP/retry-fake-git"
cat > "$RETRY_FAKE_GIT" <<'SH'
#!/usr/bin/env bash
state="${RETRY_FAKE_STATE:?}"
if [[ "$1" == "-C" ]]; then repo="$2"; shift 2; else repo=""; fi
if [[ "$1" == "ls-remote" ]]; then
  output="$(/usr/bin/git -C "$repo" "$@")"
  if [[ -n "$output" && ! -e "$state" ]]; then
    touch "$state"
    printf '%s\n' "$output" | sed 's/^[0-9a-f][0-9a-f]*/0000000000000000000000000000000000000000/'
    exit 0
  fi
  printf '%s\n' "$output"
  exit 0
fi
exec /usr/bin/git -C "$repo" "$@"
SH
chmod +x "$RETRY_FAKE_GIT"
RETRY_BIN="$TMP/retry-bin"
mkdir "$RETRY_BIN"
ln -s "$RETRY_FAKE_GIT" "$RETRY_BIN/git"
RETRY_STATE="$TMP/retry-state"
RETRY_FAIL_OUT="$(PATH="$RETRY_BIN:$PATH" RETRY_FAKE_STATE="$RETRY_STATE" "$CLI" preserve "$WT_RETRY" 2>&1)"; RETRY_FAIL_RC=$?
RETRY_JSON="$(printf '%s\n' "$RETRY_FAIL_OUT" | tail -n 1)"
RETRY_BRANCH="archive/wt-retry-$(git -C "$WT_RETRY" rev-parse HEAD | cut -c1-12)"
if [[ "$RETRY_FAIL_RC" -ne 0 && -n "$RETRY_BRANCH" && ! -f "$(git -C "$REPO" rev-parse --git-path worktree-archives)/$(basename "$WT_RETRY").json" && "$(git -C "$REPO" rev-parse --verify "refs/heads/$RETRY_BRANCH" 2>/dev/null)" == "$(git -C "$REPO" ls-remote --refs origin "refs/heads/$RETRY_BRANCH" | awk '{print $1}')" ]]; then
  ok 'failed post-push verification leaves exact archive state without receipt'
else
  bad 'failed post-push verification leaves exact archive state without receipt' "rc=$RETRY_FAIL_RC output=$RETRY_FAIL_OUT branch=$RETRY_BRANCH"
fi
RETRY_OK_OUT="$($CLI preserve "$WT_RETRY" 2>&1)"; RETRY_OK_RC=$?
if [[ "$RETRY_OK_RC" -eq 0 && -f "$REPO/$(git -C "$REPO" rev-parse --git-path worktree-archives)/retry.json" ]]; then
  ok 'preserve retries after failed post-push verification'
else
  bad 'preserve retries after failed post-push verification' "rc=$RETRY_OK_RC output=$RETRY_OK_OUT"
fi

WT_RACE="$(new_worktree "$REPO" race)"
printf 'race source\n' > "$WT_RACE/src/race.py"
RACE_FAKE_GIT="$TMP/race-fake-git"
cat > "$RACE_FAKE_GIT" <<'SH'
#!/usr/bin/env bash
if [[ "$1" == "-C" ]]; then repo="$2"; shift 2; else repo=""; fi
if [[ "$1" == "add" && "$2" == "-A" ]]; then
  touch "$repo/.env-race"
fi
exec /usr/bin/git -C "$repo" "$@"
SH
chmod +x "$RACE_FAKE_GIT"
RACE_BIN="$TMP/race-bin"; mkdir "$RACE_BIN"; ln -s "$RACE_FAKE_GIT" "$RACE_BIN/git"
RACE_OUT="$(PATH="$RACE_BIN:$PATH" "$CLI" preserve "$WT_RACE" 2>&1)"; RACE_RC=$?
if [[ "$RACE_RC" -ne 0 && "$RACE_OUT" == *'changed during preservation'* && ! -e "$REPO/.git/worktree-archives/race.json" ]]; then
  ok 'preserve rejects files created during archival'
else
  bad 'preserve rejects files created during archival' "rc=$RACE_RC output=$RACE_OUT"
fi

INVALID_RESTORE_OUT="$(cd "$REPO" && "$CLI" restore "$ARCHIVE_BRANCH" "$REPO/.worktrees/../escape" 2>&1)"; INVALID_RESTORE_RC=$?
if [[ "$INVALID_RESTORE_RC" -ne 0 && "$INVALID_RESTORE_OUT" == *'path traversal'* ]]; then ok 'restore refuses traversal destination'; else bad 'restore refuses traversal destination' "rc=$INVALID_RESTORE_RC output=$INVALID_RESTORE_OUT"; fi

printf '\nPASS=%s FAIL=%s\n' "$PASS" "$FAIL"
[[ "$FAIL" -eq 0 ]]
