#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

level=$1
if [ -z "$level" ]; then
  level=1
else 
  # remove the level parameter
  shift
fi
echo "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$1>"

#echo "sourcing init"
source this
source test.suite

log.level $level

competionArray=(once config list file ite)
source oo

source osshLayout

# ============================================================================
# Test osshLayout.id.from.email - email to oosh ssh id transform
# ============================================================================

test.case $level "id.from.email lowercases and replaces @ with ." \
  osshLayout.id.from.email "Foo.Bar@Gmail.com"
expect 0 "foo.bar.gmail.com" "Foo.Bar@Gmail.com -> foo.bar.gmail.com"

test.case $level "id.from.email handles already-lowercase plain email" \
  osshLayout.id.from.email "nortje.johannes@gmail.com"
expect 0 "nortje.johannes.gmail.com" "nortje.johannes@gmail.com -> nortje.johannes.gmail.com"

test.case $level "id.from.email rejects empty input" \
  osshLayout.id.from.email ""
expect 1 "*" "empty input returns 1"

test.case $level "id.from.email rejects input without @" \
  osshLayout.id.from.email "no-at-sign"
expect 2 "*" "no-@ input returns 2"

# ============================================================================
# Test osshLayout.role.owner - keypair + symlink layout
# ============================================================================

TEST_OSSHLAYOUT_TMP="$(mktemp -d)"
trap 'rm -rf "$TEST_OSSHLAYOUT_TMP" 2>/dev/null' EXIT

osshLayout.role.owner.test() {
  export USER_EMAIL="test.user@example.com"
  osshLayout.role.owner "$@"
  local rc=$?
  unset USER_EMAIL
  return $rc
}

TEST_OWNER_DIR="$TEST_OSSHLAYOUT_TMP/owner-fresh"
test.case $level "role.owner generates id_ed25519 in fresh sshDir" \
  osshLayout.role.owner.test "$TEST_OWNER_DIR"
if [ -f "$TEST_OWNER_DIR/id_ed25519" ] && [ -f "$TEST_OWNER_DIR/id_ed25519.pub" ]; then
  expect.pass "id_ed25519 keypair generated"
else
  expect.fail "id_ed25519 keypair not generated in $TEST_OWNER_DIR"
fi

if [ -f "$TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key" ] \
   && [ ! -L "$TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key" ]; then
  expect.pass "private_key real-file copy exists with ssh.<id> naming"
else
  expect.fail "expected real-file (not symlink) $TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key"
fi

if cmp -s "$TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key" "$TEST_OWNER_DIR/id_ed25519"; then
  expect.pass "private_key file is byte-identical to id_ed25519"
else
  expect.fail "private_key/ copy differs from id_ed25519"
fi

if [ -f "$TEST_OWNER_DIR/public_keys/ssh.test.user.example.com.public_key" ] \
   && [ ! -L "$TEST_OWNER_DIR/public_keys/ssh.test.user.example.com.public_key" ]; then
  expect.pass "public_keys real-file copy exists with ssh.<id> naming"
else
  expect.fail "expected real-file (not symlink) $TEST_OWNER_DIR/public_keys/ssh.test.user.example.com.public_key"
fi

if cmp -s "$TEST_OWNER_DIR/public_keys/ssh.test.user.example.com.public_key" "$TEST_OWNER_DIR/id_ed25519.pub"; then
  expect.pass "public_keys file is byte-identical to id_ed25519.pub"
else
  expect.fail "public_keys/ copy differs from id_ed25519.pub"
fi

# Idempotence: second call with same dir is a no-op (no error, same files)
test.case $level "role.owner is idempotent on already-built sshDir" \
  osshLayout.role.owner.test "$TEST_OWNER_DIR"
if [ -f "$TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key" ] \
   && [ ! -L "$TEST_OWNER_DIR/private_key/ssh.test.user.example.com.private_key" ]; then
  expect.pass "private_key real-file copy still present after re-run"
else
  expect.fail "private_key copy lost or became symlink after re-run"
fi

# Email resolution priority: USER_EMAIL > git config > whoami@hostname.
# Test the git-config fallback in isolation by pointing GIT_CONFIG_GLOBAL at
# a synthetic config and unsetting USER_EMAIL.
osshLayout.role.owner.git_test() {
  unset USER_EMAIL
  local fakeGitConfig="$TEST_OSSHLAYOUT_TMP/fake-gitconfig"
  printf '[user]\n\temail = git.user@example.com\n' > "$fakeGitConfig"
  export GIT_CONFIG_GLOBAL="$fakeGitConfig"
  osshLayout.role.owner "$1"
  local rc=$?
  unset GIT_CONFIG_GLOBAL
  rm -f "$fakeGitConfig"
  return $rc
}

TEST_OWNER_GIT_DIR="$TEST_OSSHLAYOUT_TMP/owner-git"
test.case $level "role.owner falls back to git config user.email when USER_EMAIL unset" \
  osshLayout.role.owner.git_test "$TEST_OWNER_GIT_DIR"
if [ -f "$TEST_OWNER_GIT_DIR/private_key/ssh.git.user.example.com.private_key" ] \
   && [ ! -L "$TEST_OWNER_GIT_DIR/private_key/ssh.git.user.example.com.private_key" ]; then
  expect.pass "git config email resolves to ssh.git.user.example.com"
else
  expect.fail "expected ssh.git.user.example.com.private_key (real file), git config fallback did not fire"
fi

# OOSH_SSH_CONFIG_HOST takes priority over hostname when no email is set, so
# the owner-id matches the meaningful OOSH alias rather than the raw hostname.
osshLayout.role.owner.alias_test() {
  unset USER_EMAIL
  local fakeHome="$TEST_OSSHLAYOUT_TMP/alias-home"
  mkdir -p "$fakeHome"
  : > "$fakeHome/.gitconfig"
  HOME="$fakeHome" GIT_CONFIG_GLOBAL="$fakeHome/.gitconfig" \
    OOSH_SSH_CONFIG_HOST="docker.once.ssh" \
    osshLayout.role.owner "$1"
  return $?
}

TEST_OWNER_ALIAS_DIR="$TEST_OSSHLAYOUT_TMP/owner-alias"
test.case $level "role.owner uses OOSH_SSH_CONFIG_HOST over hostname when no email set" \
  osshLayout.role.owner.alias_test "$TEST_OWNER_ALIAS_DIR"
WHOAMI_LOWER=$(whoami | tr '[:upper:]' '[:lower:]')
if [ -f "$TEST_OWNER_ALIAS_DIR/private_key/ssh.${WHOAMI_LOWER}.docker.once.ssh.private_key" ] \
   && [ ! -L "$TEST_OWNER_ALIAS_DIR/private_key/ssh.${WHOAMI_LOWER}.docker.once.ssh.private_key" ]; then
  expect.pass "owner-id uses whoami@OOSH_SSH_CONFIG_HOST: ssh.${WHOAMI_LOWER}.docker.once.ssh"
else
  expect.fail "expected ssh.${WHOAMI_LOWER}.docker.once.ssh.private_key (real file), OOSH_SSH_CONFIG_HOST not honoured"
fi

# role.owner must prune user.init residue: legacy <user>.<hostname>.private_key
# copies in private_key/, plus byte-for-byte duplicates of id_ed25519.pub in
# public_keys/. Peer pubkeys (different content) must survive.
TEST_OWNER_PRUNE_DIR="$TEST_OSSHLAYOUT_TMP/owner-prune"
mkdir -p "$TEST_OWNER_PRUNE_DIR/private_key" "$TEST_OWNER_PRUNE_DIR/public_keys"
ssh-keygen -t ed25519 -f "$TEST_OWNER_PRUNE_DIR/id_ed25519" -N '' -q
# Drop a legacy private_key copy and an owner-pubkey duplicate, plus a real peer pubkey.
cp "$TEST_OWNER_PRUNE_DIR/id_ed25519" "$TEST_OWNER_PRUNE_DIR/private_key/legacy.host.private_key"
cp "$TEST_OWNER_PRUNE_DIR/id_ed25519.pub" "$TEST_OWNER_PRUNE_DIR/public_keys/legacy.host.public_key"
ssh-keygen -t ed25519 -f "$TEST_OSSHLAYOUT_TMP/peer-key" -N '' -q
cp "$TEST_OSSHLAYOUT_TMP/peer-key.pub" "$TEST_OWNER_PRUNE_DIR/public_keys/ssh.peer.example.com.public_key"

osshLayout.role.owner.prune_test() {
  export USER_EMAIL="prune.user@example.com"
  osshLayout.role.owner "$1"
  local rc=$?
  unset USER_EMAIL
  return $rc
}

test.case $level "role.owner prunes legacy private_key copies + dup pubkeys, keeps peers" \
  osshLayout.role.owner.prune_test "$TEST_OWNER_PRUNE_DIR"

if [ ! -e "$TEST_OWNER_PRUNE_DIR/private_key/legacy.host.private_key" ]; then
  expect.pass "legacy private_key copy removed"
else
  expect.fail "legacy.host.private_key should have been pruned"
fi
if [ ! -e "$TEST_OWNER_PRUNE_DIR/public_keys/legacy.host.public_key" ]; then
  expect.pass "duplicate owner pubkey removed from public_keys/"
else
  expect.fail "legacy.host.public_key (dup of id_ed25519.pub) should have been pruned"
fi
if [ -e "$TEST_OWNER_PRUNE_DIR/public_keys/ssh.peer.example.com.public_key" ]; then
  expect.pass "peer pubkey (different content) preserved"
else
  expect.fail "peer pubkey should have been kept"
fi
if [ -f "$TEST_OWNER_PRUNE_DIR/private_key/ssh.prune.user.example.com.private_key" ] \
   && [ ! -L "$TEST_OWNER_PRUNE_DIR/private_key/ssh.prune.user.example.com.private_key" ]; then
  expect.pass "canonical owner real-file copy in place after prune"
else
  expect.fail "canonical owner copy missing after prune"
fi

# ============================================================================
# Test osshLayout.role.developking - template deployment
# ============================================================================

TEST_DK_DIR="$TEST_OSSHLAYOUT_TMP/dk-fresh"
test.case $level "role.developking deploys template into ids/ssh.developking/" \
  osshLayout.role.developking "$TEST_DK_DIR"

if [ -f "$TEST_DK_DIR/ids/ssh.developking/id_rsa" ] && [ -f "$TEST_DK_DIR/ids/ssh.developking/id_rsa.pub" ]; then
  expect.pass "developking id_rsa keypair present"
else
  expect.fail "expected id_rsa pair in $TEST_DK_DIR/ids/ssh.developking/"
fi

if [ -f "$TEST_DK_DIR/ids/ssh.developking/private_key/developer.wo-da.de.private_key" ]; then
  expect.pass "developer.wo-da.de.private_key present in private_key/"
else
  expect.fail "developer.wo-da.de.private_key missing"
fi

if [ -f "$TEST_DK_DIR/ids/ssh.developking/public_keys/developer.wo-da.de.public_key" ]; then
  expect.pass "developer.wo-da.de.public_key present"
else
  expect.fail "developer.wo-da.de.public_key missing"
fi

if [ -f "$TEST_DK_DIR/ids/ssh.developking/public_keys/donges.mcdonges.fritz.box.public_key" ]; then
  expect.pass "donges.mcdonges.fritz.box.public_key present (peer)"
else
  expect.fail "donges.mcdonges.fritz.box.public_key missing"
fi

if [ -f "$TEST_DK_DIR/ids/ssh.developking/known_hosts" ]; then
  expect.pass "developking known_hosts present"
else
  expect.fail "developking known_hosts missing"
fi

# ============================================================================
# Test osshLayout.role.installer - copy installer keypair from source dir
# ============================================================================

# Stage a fake installer keypair (ed25519)
TEST_INSTALLER_SRC="$TEST_OSSHLAYOUT_TMP/installer-src"
mkdir -p "$TEST_INSTALLER_SRC"
ssh-keygen -t ed25519 -f "$TEST_INSTALLER_SRC/id_ed25519" -N '' -q

TEST_INSTALLER_DST="$TEST_OSSHLAYOUT_TMP/installer-fresh"
test.case $level "role.installer copies ed25519 keypair into ids/ssh.<id>/" \
  osshLayout.role.installer "Foo.Bar@Example.com" "$TEST_INSTALLER_SRC" "$TEST_INSTALLER_DST"

INSTALLER_DST_DIR="$TEST_INSTALLER_DST/ids/ssh.foo.bar.example.com"
if [ -f "$INSTALLER_DST_DIR/id_ed25519" ] && [ -f "$INSTALLER_DST_DIR/id_ed25519.pub" ]; then
  expect.pass "installer id_ed25519 keypair copied"
else
  expect.fail "expected id_ed25519 pair in $INSTALLER_DST_DIR"
fi

# Portable octal-perms read: GNU stat (-c '%a') on Linux, BSD stat (-f '%Lp') on macOS.
PRIV_PERMS=$(stat -c '%a' "$INSTALLER_DST_DIR/id_ed25519" 2>/dev/null \
              || stat -f '%Lp' "$INSTALLER_DST_DIR/id_ed25519" 2>/dev/null)
if [ "$PRIV_PERMS" = "600" ]; then
  expect.pass "installer private key permissions are 600"
else
  expect.fail "expected 600 perms on private key, got '$PRIV_PERMS'"
fi

if [ -f "$INSTALLER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" ] \
   && [ ! -L "$INSTALLER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" ] \
   && cmp -s "$INSTALLER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" "$INSTALLER_DST_DIR/id_ed25519"; then
  expect.pass "installer private_key/ is real file byte-equal to id_ed25519"
else
  expect.fail "installer private_key/ is missing, a symlink, or differs from id_ed25519"
fi

if [ -f "$INSTALLER_DST_DIR/public_keys/ssh.foo.bar.example.com.public_key" ] \
   && [ ! -L "$INSTALLER_DST_DIR/public_keys/ssh.foo.bar.example.com.public_key" ] \
   && cmp -s "$INSTALLER_DST_DIR/public_keys/ssh.foo.bar.example.com.public_key" "$INSTALLER_DST_DIR/id_ed25519.pub"; then
  expect.pass "installer public_keys/ is real file byte-equal to id_ed25519.pub"
else
  expect.fail "installer public_keys/ is missing, a symlink, or differs from id_ed25519.pub"
fi

# Test rsa fallback path with a separate source dir
TEST_INSTALLER_SRC_RSA="$TEST_OSSHLAYOUT_TMP/installer-src-rsa"
mkdir -p "$TEST_INSTALLER_SRC_RSA"
ssh-keygen -t rsa -b 2048 -f "$TEST_INSTALLER_SRC_RSA/id_rsa" -N '' -q

TEST_INSTALLER_DST_RSA="$TEST_OSSHLAYOUT_TMP/installer-rsa-fresh"
test.case $level "role.installer falls back to id_rsa when no id_ed25519" \
  osshLayout.role.installer "rsa.user@example.com" "$TEST_INSTALLER_SRC_RSA" "$TEST_INSTALLER_DST_RSA"

if [ -f "$TEST_INSTALLER_DST_RSA/ids/ssh.rsa.user.example.com/id_rsa" ]; then
  expect.pass "installer fallback id_rsa keypair copied"
else
  expect.fail "expected id_rsa fallback in $TEST_INSTALLER_DST_RSA"
fi

# Test missing source keypair returns error
TEST_EMPTY_SRC="$TEST_OSSHLAYOUT_TMP/empty-src"
mkdir -p "$TEST_EMPTY_SRC"
test.case $level "role.installer errors when source has no keypair" \
  osshLayout.role.installer "x@y.com" "$TEST_EMPTY_SRC" "$TEST_OSSHLAYOUT_TMP/no-keys-dst"
expect 2 "*" "missing keypair returns 2"

# ============================================================================
# Test osshLayout.role.outeruser - passthrough mirror from source dir
# ============================================================================

# Use the installer dst from the previous test as the source for outeruser
# (this mirrors the os platform.test scenario where outer == installer)
TEST_OUTER_DST="$TEST_OSSHLAYOUT_TMP/outer-fresh"
test.case $level "role.outeruser mirrors a formatted source into ids/ssh.outeruser/" \
  osshLayout.role.outeruser "$INSTALLER_DST_DIR" "$TEST_OUTER_DST"

OUTER_DST_DIR="$TEST_OUTER_DST/ids/ssh.outeruser"
if [ -f "$OUTER_DST_DIR/id_ed25519" ] && [ -f "$OUTER_DST_DIR/id_ed25519.pub" ]; then
  expect.pass "outeruser id_ed25519 keypair mirrored"
else
  expect.fail "expected id_ed25519 pair in $OUTER_DST_DIR"
fi

# Real-file copy should be present and byte-equal to id_ed25519 inside outeruser dir.
if [ -f "$OUTER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" ] \
   && [ ! -L "$OUTER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" ]; then
  expect.pass "outeruser private_key/ real-file copy preserved"
else
  expect.fail "outeruser private_key/ missing or unexpectedly a symlink"
fi

if cmp -s "$OUTER_DST_DIR/private_key/ssh.foo.bar.example.com.private_key" "$OUTER_DST_DIR/id_ed25519"; then
  expect.pass "outeruser private_key/ copy is byte-equal to id_ed25519 inside outeruser dir"
else
  expect.fail "outeruser private_key/ copy differs from $OUTER_DST_DIR/id_ed25519"
fi

# Test bare-bones source (only id_ed25519, no private_key/public_keys folders)
TEST_BARE_SRC="$TEST_OSSHLAYOUT_TMP/outer-bare-src"
mkdir -p "$TEST_BARE_SRC"
ssh-keygen -t ed25519 -f "$TEST_BARE_SRC/id_ed25519" -N '' -q

TEST_BARE_DST="$TEST_OSSHLAYOUT_TMP/outer-bare-dst"
test.case $level "role.outeruser mirrors a bare-bones source (keys only)" \
  osshLayout.role.outeruser "$TEST_BARE_SRC" "$TEST_BARE_DST"

BARE_DST_DIR="$TEST_BARE_DST/ids/ssh.outeruser"
if [ -f "$BARE_DST_DIR/id_ed25519" ] && [ -f "$BARE_DST_DIR/id_ed25519.pub" ]; then
  expect.pass "outeruser bare-bones keys mirrored"
else
  expect.fail "expected bare keys in $BARE_DST_DIR"
fi

if [ ! -d "$BARE_DST_DIR/private_key" ]; then
  expect.pass "outeruser bare-bones has no private_key/ subfolder"
else
  expect.fail "bare source should not have produced private_key/"
fi

# Missing source
test.case $level "role.outeruser errors on missing source" \
  osshLayout.role.outeruser "/nonexistent/path/123" "$TEST_OSSHLAYOUT_TMP/outer-error"
expect 2 "*" "missing source returns 2"

# ============================================================================
# Test osshLayout.build - end-to-end orchestration of all four roles
# ============================================================================

TEST_BUILD_KEYSRC="$TEST_OSSHLAYOUT_TMP/build-keysrc"
mkdir -p "$TEST_BUILD_KEYSRC"
ssh-keygen -t ed25519 -f "$TEST_BUILD_KEYSRC/id_ed25519" -N '' -q

TEST_BUILD_TMP="$TEST_OSSHLAYOUT_TMP/build-fresh"

osshLayout.build.test() {
  export USER_EMAIL="owner@example.com"
  osshLayout.build "installer@example.com" "$TEST_BUILD_KEYSRC" "$TEST_BUILD_TMP"
  local rc=$?
  unset USER_EMAIL
  return $rc
}

test.case $level "build orchestrates owner+developking+installer+outeruser" \
  osshLayout.build.test

# Owner (USER_EMAIL=owner@example.com)
if [ -f "$TEST_BUILD_TMP/id_ed25519" ] \
   && [ -f "$TEST_BUILD_TMP/private_key/ssh.owner.example.com.private_key" ] \
   && [ ! -L "$TEST_BUILD_TMP/private_key/ssh.owner.example.com.private_key" ]; then
  expect.pass "build produced owner identity (real-file copy)"
else
  expect.fail "build did not produce owner identity (or it is a symlink)"
fi

# Developking
if [ -f "$TEST_BUILD_TMP/ids/ssh.developking/id_rsa" ]; then
  expect.pass "build produced developking identity"
else
  expect.fail "build did not produce developking identity"
fi

# Installer (passed installer@example.com)
if [ -f "$TEST_BUILD_TMP/ids/ssh.installer.example.com/id_ed25519" ] \
   && [ -f "$TEST_BUILD_TMP/ids/ssh.installer.example.com/private_key/ssh.installer.example.com.private_key" ] \
   && [ ! -L "$TEST_BUILD_TMP/ids/ssh.installer.example.com/private_key/ssh.installer.example.com.private_key" ]; then
  expect.pass "build produced installer identity (real-file copy)"
else
  expect.fail "build did not produce installer identity (or it is a symlink)"
fi

# Outeruser (mirror of installer in test-mode default)
if [ -f "$TEST_BUILD_TMP/ids/ssh.outeruser/id_ed25519" ]; then
  expect.pass "build produced outeruser identity (mirror of installer)"
else
  expect.fail "build did not produce outeruser identity"
fi

# ============================================================================
# Test staged identity (the os.platform.test / ProxyJump production path).
# Builds drive role.installer + role.outeruser from $OSSH_STAGE_DIR/oosh.<role>.*
# files placed by ossh.install (private.ossh.identity.stage) before init/oosh.
# ============================================================================

# Helper: stage a role into <stageDir>/oosh.<role>.{email,id_ed25519,id_ed25519.pub}
test.osshLayout.stage() {
  local stageDir="$1" role="$2" email="$3" privKey="$4"
  printf '%s\n' "$email" > "${stageDir}/oosh.${role}.email"
  cp "$privKey" "${stageDir}/oosh.${role}.id_ed25519"
  cp "${privKey}.pub" "${stageDir}/oosh.${role}.id_ed25519.pub"
  chmod 600 "${stageDir}/oosh.${role}.id_ed25519"
}

# --- Test mode collapse: same staged content for installer and outer ---
TEST_STAGE_COLLAPSE="$TEST_OSSHLAYOUT_TMP/stage-collapse"
mkdir -p "$TEST_STAGE_COLLAPSE"
ssh-keygen -t ed25519 -f "$TEST_STAGE_COLLAPSE/host-key" -N '' -q
test.osshLayout.stage "$TEST_STAGE_COLLAPSE" installer "host.runner@example.com" "$TEST_STAGE_COLLAPSE/host-key"
test.osshLayout.stage "$TEST_STAGE_COLLAPSE" outer     "host.runner@example.com" "$TEST_STAGE_COLLAPSE/host-key"

osshLayout.build.test.staged_collapse() {
  export USER_EMAIL="container.user@example.com"
  export OSSH_STAGE_DIR="$TEST_STAGE_COLLAPSE"
  osshLayout.build "" "" "$TEST_OSSHLAYOUT_TMP/staged-collapse-fresh"
  local rc=$?
  unset USER_EMAIL OSSH_STAGE_DIR
  return $rc
}

test.case $level "build with same staged installer + outer (test mode) — distinct folders, byte-identical" \
  osshLayout.build.test.staged_collapse

COLLAPSE_DIR="$TEST_OSSHLAYOUT_TMP/staged-collapse-fresh"
# Owner identity comes from USER_EMAIL (container.user@example.com), not staging
if [ -f "$COLLAPSE_DIR/private_key/ssh.container.user.example.com.private_key" ] \
   && [ ! -L "$COLLAPSE_DIR/private_key/ssh.container.user.example.com.private_key" ]; then
  expect.pass "owner uses USER_EMAIL identity (container user), independent of staging"
else
  expect.fail "owner real-file copy for container.user.example.com missing"
fi

# Installer identity comes from staged installer.email
if [ -d "$COLLAPSE_DIR/ids/ssh.host.runner.example.com" ] \
   && [ -f "$COLLAPSE_DIR/ids/ssh.host.runner.example.com/private_key/ssh.host.runner.example.com.private_key" ] \
   && [ ! -L "$COLLAPSE_DIR/ids/ssh.host.runner.example.com/private_key/ssh.host.runner.example.com.private_key" ]; then
  expect.pass "installer folder named after staged email (host.runner.example.com)"
else
  expect.fail "expected ids/ssh.host.runner.example.com/ from staged installer"
fi

# Outeruser real-file copies named after staged outer.email (also host.runner.example.com here)
if [ -f "$COLLAPSE_DIR/ids/ssh.outeruser/private_key/ssh.host.runner.example.com.private_key" ] \
   && [ ! -L "$COLLAPSE_DIR/ids/ssh.outeruser/private_key/ssh.host.runner.example.com.private_key" ]; then
  expect.pass "outeruser real-file copies named after staged outer email (collapsed)"
else
  expect.fail "expected real-file ssh.outeruser/private_key/ssh.host.runner.example.com.private_key"
fi

# Bytes identical (same staged file → same content)
if cmp -s "$COLLAPSE_DIR/ids/ssh.host.runner.example.com/id_ed25519" \
          "$COLLAPSE_DIR/ids/ssh.outeruser/id_ed25519"; then
  expect.pass "installer and outeruser keypair are byte-identical (test-mode collapse)"
else
  expect.fail "installer and outeruser bytes differ — test-mode collapse broken"
fi

# Container user folder must NOT appear under ids/ — owner identity lives only at top level
if [ ! -d "$COLLAPSE_DIR/ids/ssh.container.user.example.com" ]; then
  expect.pass "no ids/ssh.<owner>/ folder (owner is top-level only)"
else
  expect.fail "owner identity should not appear under ids/"
fi

# --- ProxyJump shape: different staged content for installer and outer ---
TEST_STAGE_PJ="$TEST_OSSHLAYOUT_TMP/stage-proxyjump"
mkdir -p "$TEST_STAGE_PJ"
ssh-keygen -t ed25519 -f "$TEST_STAGE_PJ/laptop-key" -N '' -q
ssh-keygen -t ed25519 -f "$TEST_STAGE_PJ/jump-key"   -N '' -q
test.osshLayout.stage "$TEST_STAGE_PJ" installer "alice@laptop.example.com" "$TEST_STAGE_PJ/laptop-key"
test.osshLayout.stage "$TEST_STAGE_PJ" outer     "ops@jumphost.example.com" "$TEST_STAGE_PJ/jump-key"

osshLayout.build.test.staged_proxyjump() {
  export USER_EMAIL="prod.user@example.com"
  export OSSH_STAGE_DIR="$TEST_STAGE_PJ"
  osshLayout.build "" "" "$TEST_OSSHLAYOUT_TMP/staged-pj-fresh"
  local rc=$?
  unset USER_EMAIL OSSH_STAGE_DIR
  return $rc
}

test.case $level "build with different staged installer + outer (ProxyJump shape) — distinct identities" \
  osshLayout.build.test.staged_proxyjump

PJ_DIR="$TEST_OSSHLAYOUT_TMP/staged-pj-fresh"
if [ -d "$PJ_DIR/ids/ssh.alice.laptop.example.com" ]; then
  expect.pass "installer folder uses laptop identity"
else
  expect.fail "expected ids/ssh.alice.laptop.example.com/"
fi
if [ -f "$PJ_DIR/ids/ssh.outeruser/private_key/ssh.ops.jumphost.example.com.private_key" ] \
   && [ ! -L "$PJ_DIR/ids/ssh.outeruser/private_key/ssh.ops.jumphost.example.com.private_key" ]; then
  expect.pass "outeruser real-file copies use jump host identity (distinct from installer)"
else
  expect.fail "outeruser real-file copies should be named after jump host email"
fi
if ! cmp -s "$PJ_DIR/ids/ssh.alice.laptop.example.com/id_ed25519" \
            "$PJ_DIR/ids/ssh.outeruser/id_ed25519"; then
  expect.pass "installer (laptop) and outeruser (jump) keypair bytes differ — ProxyJump shape preserved"
else
  expect.fail "installer and outeruser bytes match — but they came from different staged keys"
fi

# --- Real ProxyJump shape: outer staged with pubkey ONLY (no private) ---
# Simulates the actual production scenario where the runner pulls the jump
# host's public key but cannot exfiltrate its private key.
TEST_STAGE_PJ_PUBONLY="$TEST_OSSHLAYOUT_TMP/stage-pj-pubonly"
mkdir -p "$TEST_STAGE_PJ_PUBONLY"
ssh-keygen -t ed25519 -f "$TEST_STAGE_PJ_PUBONLY/laptop-key" -N '' -q
ssh-keygen -t ed25519 -f "$TEST_STAGE_PJ_PUBONLY/jump-key"   -N '' -q
test.osshLayout.stage "$TEST_STAGE_PJ_PUBONLY" installer "alice@laptop.example.com" "$TEST_STAGE_PJ_PUBONLY/laptop-key"
# Stage outer email + pubkey only — deliberately NO private key file
printf '%s\n' "ops@jumphost.example.com" > "$TEST_STAGE_PJ_PUBONLY/oosh.outer.email"
cp "$TEST_STAGE_PJ_PUBONLY/jump-key.pub" "$TEST_STAGE_PJ_PUBONLY/oosh.outer.id_ed25519.pub"

osshLayout.build.test.staged_pubonly() {
  export USER_EMAIL="prod.user@example.com"
  export OSSH_STAGE_DIR="$TEST_STAGE_PJ_PUBONLY"
  osshLayout.build "" "" "$TEST_OSSHLAYOUT_TMP/staged-pubonly-fresh"
  local rc=$?
  unset USER_EMAIL OSSH_STAGE_DIR
  return $rc
}

test.case $level "build with pubkey-only outer (real ProxyJump) — outeruser has no private key" \
  osshLayout.build.test.staged_pubonly

PUBONLY_DIR="$TEST_OSSHLAYOUT_TMP/staged-pubonly-fresh"
PUBONLY_OUTER="$PUBONLY_DIR/ids/ssh.outeruser"

if [ -f "$PUBONLY_OUTER/id_ed25519.pub" ] && [ ! -e "$PUBONLY_OUTER/id_ed25519" ]; then
  expect.pass "outeruser has id_ed25519.pub but no id_ed25519 (private absent)"
else
  expect.fail "expected pubkey-only outer: have id_ed25519=$([ -e "$PUBONLY_OUTER/id_ed25519" ] && echo yes || echo no), have id_ed25519.pub=$([ -f "$PUBONLY_OUTER/id_ed25519.pub" ] && echo yes || echo no)"
fi

if [ ! -d "$PUBONLY_OUTER/private_key" ]; then
  expect.pass "outeruser has no private_key/ folder (pubkey-only shape)"
else
  expect.fail "private_key/ folder should not exist for pubkey-only outer"
fi

if [ -f "$PUBONLY_OUTER/public_keys/ssh.ops.jumphost.example.com.public_key" ] \
   && [ ! -L "$PUBONLY_OUTER/public_keys/ssh.ops.jumphost.example.com.public_key" ]; then
  expect.pass "outeruser public_keys real-file copy uses jump host email-as-id"
else
  expect.fail "expected real-file public_keys/ssh.ops.jumphost.example.com.public_key"
fi

if cmp -s "$PUBONLY_OUTER/public_keys/ssh.ops.jumphost.example.com.public_key" "$PUBONLY_OUTER/id_ed25519.pub"; then
  expect.pass "outeruser public_keys copy is byte-equal to id_ed25519.pub"
else
  expect.fail "outeruser public_keys copy differs from id_ed25519.pub"
fi

# Installer side still has full keypair (laptop) — pubkey-only outer doesn't affect installer
if [ -f "$PUBONLY_DIR/ids/ssh.alice.laptop.example.com/id_ed25519" ] \
   && [ -f "$PUBONLY_DIR/ids/ssh.alice.laptop.example.com/id_ed25519.pub" ]; then
  expect.pass "installer side still has full keypair (laptop), unaffected by outer being pubkey-only"
else
  expect.fail "installer keypair missing or incomplete"
fi

# ============================================================================
# Test osshLayout perms + real-file invariants (no-symlinks-in-private_key)
# ============================================================================
# Re-uses the staged_collapse layout already built above as $COLLAPSE_DIR.

test.case $level "perms: $COLLAPSE_DIR is mode 700" true
cMode=$(stat -c '%a' "$COLLAPSE_DIR" 2>/dev/null || stat -f '%Lp' "$COLLAPSE_DIR" 2>/dev/null)
[ "$cMode" = "700" ] && expect.pass "700" || expect.fail "got $cMode"

test.case $level "perms: $COLLAPSE_DIR/ids is mode 700" true
iMode=$(stat -c '%a' "$COLLAPSE_DIR/ids" 2>/dev/null || stat -f '%Lp' "$COLLAPSE_DIR/ids" 2>/dev/null)
[ "$iMode" = "700" ] && expect.pass "700" || expect.fail "got $iMode"

# Owner-level private_key/ files: real (not symlink), mode 600, byte-equal to id_ed25519
test.case $level "owner private_key/ files are real + 600 + match id_ed25519" true
ownerBad=""
for f in "$COLLAPSE_DIR/private_key"/*; do
  [ -e "$f" ] || continue
  [ -L "$f" ] && ownerBad="$ownerBad [symlink:$f]" && continue
  m=$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f" 2>/dev/null)
  [ "$m" = "600" ] || ownerBad="$ownerBad [mode$m:$f]"
  cmp -s "$f" "$COLLAPSE_DIR/id_ed25519" 2>/dev/null || ownerBad="$ownerBad [bytes:$f]"
done
[ -z "$ownerBad" ] && expect.pass "ok" || expect.fail "$ownerBad"

# Owner-level public_keys/ files: real (not symlink), mode 644, byte-equal to id_ed25519.pub
test.case $level "owner public_keys/ files are real + 644 + match id_ed25519.pub" true
ownerPubBad=""
for f in "$COLLAPSE_DIR/public_keys"/*; do
  [ -e "$f" ] || continue
  [ -L "$f" ] && ownerPubBad="$ownerPubBad [symlink:$f]" && continue
  m=$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f" 2>/dev/null)
  [ "$m" = "644" ] || ownerPubBad="$ownerPubBad [mode$m:$f]"
  cmp -s "$f" "$COLLAPSE_DIR/id_ed25519.pub" 2>/dev/null || ownerPubBad="$ownerPubBad [bytes:$f]"
done
[ -z "$ownerPubBad" ] && expect.pass "ok" || expect.fail "$ownerPubBad"

# Every ids/ssh.*/private_key/ subdir is mode 700; every file inside is real + 600.
# Every ids/ssh.*/public_keys/ subdir is mode 700; every file inside is real + 644.
test.case $level "ids/ subtree: 700 dirs, real-file 600 priv, real-file 644 pub" true
nestedBad=""
for d in "$COLLAPSE_DIR/ids"/ssh.*; do
  [ -d "$d" ] || continue
  dMode=$(stat -c '%a' "$d" 2>/dev/null || stat -f '%Lp' "$d" 2>/dev/null)
  [ "$dMode" = "700" ] || nestedBad="$nestedBad [$d=mode$dMode]"
  for sub in "$d/private_key" "$d/public_keys"; do
    [ -d "$sub" ] || continue
    sMode=$(stat -c '%a' "$sub" 2>/dev/null || stat -f '%Lp' "$sub" 2>/dev/null)
    [ "$sMode" = "700" ] || nestedBad="$nestedBad [$sub=mode$sMode]"
    for f in "$sub"/*; do
      [ -e "$f" ] || continue
      [ -L "$f" ] && nestedBad="$nestedBad [symlink:$f]" && continue
      fmode=$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f" 2>/dev/null)
      case "$sub" in
        */private_key) [ "$fmode" = "600" ] || nestedBad="$nestedBad [$f=mode$fmode want600]" ;;
        */public_keys) [ "$fmode" = "644" ] || nestedBad="$nestedBad [$f=mode$fmode want644]" ;;
      esac
    done
  done
done
[ -z "$nestedBad" ] && expect.pass "ok" || expect.fail "$nestedBad"

### test.method

