#!/usr/bin/env bash

# The personal half of the repository.
#
# user/ is somebody's actual agent instructions and SSH host aliases, tracked in
# a repository other people are meant to clone. That is only defensible under
# three rules, and this pins all three:
#
#   1. Nothing is linked on a machine that did not say yes. A stranger who runs
#      ./install and answers the default gets their own ~/.claude/CLAUDE.md left
#      exactly where it was.
#   2. Nothing already in a destination is destroyed. It moves to config/old/,
#      the same promise link-dotfiles makes.
#   3. No secrets. The directory is world-readable to anyone who finds the
#      repository, so a private key or token committed here is a disclosure and
#      not a mistake to be caught in review.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
linker="$repo_dir/setup/scripts/link-user"
manifest="$repo_dir/user/manifest"
interview="$repo_dir/setup/scripts/interview"
installer="$repo_dir/install"

findings=()
note() { findings+=("$1"); }

[[ -x "$linker" ]] || { printf 'user content contract: %s is not executable\n' "$linker" >&2; exit 1; }
[[ -r "$manifest" ]] || { printf 'user content contract: no manifest at %s\n' "$manifest" >&2; exit 1; }

# ── 3. No secrets ───────────────────────────────────────────────────────────
#
# Checked first, because it is the one failure that cannot be undone by fixing
# the code: a key that reached a commit is a key that must be rotated.

if grep -rlqE 'BEGIN [A-Z ]*PRIVATE KEY' "$repo_dir/user" 2>/dev/null; then
    note 'a private key is committed under user/'
fi
if grep -rlqE 'sk-ant-[A-Za-z0-9]|ghp_[A-Za-z0-9]{20}|xox[baprs]-[A-Za-z0-9]' "$repo_dir/user" 2>/dev/null; then
    note 'something that looks like an API token is committed under user/'
fi
while read -r candidate; do
    note "user/ carries a credentials file: ${candidate#"$repo_dir"/}"
done < <(find "$repo_dir/user" -type f \( -name 'id_*' -o -name '*.pem' -o -name '.credentials.json' \) 2>/dev/null)

# ── Every manifest line is usable ───────────────────────────────────────────

while read -r kind source destination; do
    [[ -e "$repo_dir/user/$source" ]] \
        || note "the manifest points at $source, which is not in user/"
    [[ "$kind" == link || "$kind" == copy ]] \
        || note "the manifest uses an unknown kind: $kind"
    [[ "$destination" == '~/'* ]] \
        || note "the manifest destination $destination is not under the home directory"
done < <(grep -vE '^\s*(#|$)' "$manifest")

# ── The behaviour, against a fake home ──────────────────────────────────────

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

# A checkout of its own, so the test never links anything into the real home and
# never moves a real file into the real config/old.
checkout="$work/Panama"
mkdir -p "$checkout/setup/scripts" "$checkout/user/agents"
cp "$linker" "$checkout/setup/scripts/link-user"
printf 'tracked instructions\n' >"$checkout/user/agents/AGENTS.md"
mkdir -p "$checkout/user/agents/skills/example"
printf 'a skill\n' >"$checkout/user/agents/skills/example/SKILL.md"
printf 'copied once\n' >"$checkout/user/plain.txt"
cat >"$checkout/user/manifest" <<'FIXTURE'
# a comment, and a blank line follow

link  agents/AGENTS.md  ~/.claude/CLAUDE.md
link  agents/skills     ~/.agents/skills
copy  plain.txt         ~/.config/plain.txt
link  missing.txt       ~/.config/missing.txt
FIXTURE

home="$work/home"
run() {
    HOME="$home" XDG_STATE_HOME="$work/state" PANAMA_PATH="$checkout" \
        "$checkout/setup/scripts/link-user" "$@" >"$work/log" 2>&1
}

# ── 1. Silence unless asked ─────────────────────────────────────────────────

mkdir -p "$home/.claude"
printf 'somebody else instructions\n' >"$home/.claude/CLAUDE.md"

run
[[ "$(cat "$home/.claude/CLAUDE.md")" == "somebody else instructions" ]] \
    || note 'personal content was linked on a machine that was never asked'
[[ -e "$home/.agents/skills" ]] \
    && note 'skills were linked on a machine that was never asked'

PANAMA_USER_CONTENT=no run
[[ "$(cat "$home/.claude/CLAUDE.md")" == "somebody else instructions" ]] \
    || note 'personal content was linked on a machine that answered no'

# ── 2. Saying yes links, and keeps what was there ───────────────────────────

PANAMA_USER_CONTENT=yes run

[[ -L "$home/.claude/CLAUDE.md" ]] \
    || note 'CLAUDE.md was not replaced with a symlink into the checkout'
[[ "$(cat "$home/.claude/CLAUDE.md")" == "tracked instructions" ]] \
    || note 'the CLAUDE.md link does not resolve to the tracked file'
[[ -L "$home/.agents/skills" && -f "$home/.agents/skills/example/SKILL.md" ]] \
    || note 'the skills directory was not linked as a directory'
[[ -f "$home/.config/plain.txt" && ! -L "$home/.config/plain.txt" ]] \
    || note 'a copy entry was linked rather than copied'

if ! grep -rq 'somebody else instructions' "$checkout/config/old" 2>/dev/null; then
    note 'the file that was already there was not preserved in config/old'
fi

# A source the manifest names but the checkout does not have is skipped, not
# fatal: half a manifest applied is better than none, and the log says which.
[[ -e "$home/.config/missing.txt" ]] \
    && note 'a manifest entry with no source produced a destination anyway'
grep -q 'missing.txt' "$work/log" \
    || note 'a skipped manifest entry was not reported'

# ── The answer sticks ───────────────────────────────────────────────────────
#
# So that `panama upgrade` on a configured machine relinks without an interview,
# and a machine that said no stays quiet forever.

printf 'edited by hand\n' >"$home/.config/plain.txt"
rm -f "$home/.claude/CLAUDE.md"
run                                    # no PANAMA_USER_CONTENT this time
[[ -L "$home/.claude/CLAUDE.md" ]] \
    || note 'the recorded answer was not remembered, so an upgrade would not relink'
[[ "$(cat "$home/.config/plain.txt")" == "edited by hand" ]] \
    || note 'a copy entry overwrote a file the machine had edited'

# Relinking twice must not bury the previous run's symlink in config/old.
before="$(find "$checkout/config/old" -type f 2>/dev/null | wc -l)"
run
after="$(find "$checkout/config/old" -type f 2>/dev/null | wc -l)"
[[ "$before" == "$after" ]] \
    || note 'relinking backed up its own symlink, so config/old grows every run'

# ── Wiring ──────────────────────────────────────────────────────────────────

grep -q 'link-user' "$installer" \
    || note 'link-user is not in the installer STAGES list, so it never runs'
grep -q 'PANAMA_USER_CONTENT' "$installer" \
    || note 'the installer does not export the personal-content answer'
grep -q 'PANAMA_USER_CONTENT' "$interview" \
    || note 'the interview never asks about personal content'

# Order matters: link-user must land the tracked espanso identity before
# setup-identity would seed one from the interview answers.
python3 - "$installer" <<'PY' || note 'link-user does not run before setup-identity'
import re, sys
line = next(l for l in open(sys.argv[1], encoding="utf-8") if l.startswith("STAGES="))
stages = re.findall(r"[\w-]+", line)
if stages.index("link-user") > stages.index("setup-identity"):
    raise SystemExit(1)
PY

if (( ${#findings[@]} > 0 )); then
    printf 'user content contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'user content contract: PASS\n'
