# Bats helpers shared by every test file.
# `load 'helpers'` from a .bats file pulls these in.

# helpers.bash lives at <repo>/tests/helpers.bash, so repo root is one level
# up from its own directory. This avoids depending on .git, which may not
# be present on remote test hosts (e.g. rsynced without history).
PARTY_BIN="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/party"

# Per-test scratch dir. Bats sets BATS_TEST_TMPDIR but we centralize so
# party's overridable env vars all point under one tree.
setup_party_sandbox() {
  # Pretend the test runner is NOT inside tmux. iTerm2's tmux -CC mode
  # (or any nested tmux) sets $TMUX, which several party subcommands
  # treat as "you're already attached somewhere." Tests that need
  # $TMUX set re-export it themselves.
  unset TMUX TMUX_PANE TMUX_TMPDIR

  # USER may not be exported in all environments (e.g. some CI shells,
  # zsh with clean env). party uses $USER under set -eu, so guarantee it.
  export USER="${USER:-$(id -un)}"

  # Pin the gating group. cmd_host auto-selects a candidate group when
  # TMUX_PARTY_GROUP is unset, which would make bare `party host` calls
  # environment-dependent; tests keep the historical explicit default.
  # Suites that test auto-selection unset this themselves.
  export TMUX_PARTY_GROUP="${TMUX_PARTY_GROUP:-party}"

  export PARTY_TMP="$BATS_TEST_TMPDIR/party"
  export PARTY_SOCKET_DIR="$PARTY_TMP/sockets"
  mkdir -p "$PARTY_SOCKET_DIR"
  chmod 0700 "$PARTY_SOCKET_DIR"
  # No shared roster directory in this layout. Each per-party dir under
  # PARTY_SOCKET_DIR carries its own roster file. validate_socket_dir_parent
  # accepts an owner-only-writable parent like this one.
}

# Tear down any tmux servers we spawned under PARTY_SOCKET_DIR.
# Sockets live at ${PARTY_SOCKET_DIR}/party-USER:NAME.d/sock under the
# per-party-private-dir layout. The old glob (party-*) matched the
# *directory* and the [ -S ] filter dropped everything, so servers
# leaked across tests.
teardown_party_sandbox() {
  if [ -d "${PARTY_SOCKET_DIR:-}" ]; then
    for d in "$PARTY_SOCKET_DIR"/party-*.d; do
      [ -d "$d" ] || continue
      sock="$d/sock"
      [ -S "$sock" ] || continue
      tmux -S "$sock" kill-server 2>/dev/null || true
    done
  fi
}

# Pre-create the per-party private dir so roster_write can land its
# record inside. Mirrors cmd_host's mkdir -m 0700 + chmod 0750. Also
# creates a placeholder sock file so roster_read (which requires
# $d/sock to exist and be owned by the basename's HOST_USER) accepts
# the dir.
ensure_party_dir() {
  local d="$PARTY_SOCKET_DIR/party-$1:$2.d"
  mkdir -p "$d"
  chmod 0750 "$d"
  : > "$d/sock"
}

# Skip the test if the running user is not a member of TMUX_PARTY_GROUP.
require_party_group() {
  local g="${TMUX_PARTY_GROUP:-party}"
  id -nG "$USER" 2>/dev/null | tr ' ' '\n' | grep -qx "$g" \
    || skip "user not in group '$g'; run: sudo usermod -aG $g $USER && relog"
}

# Print one username per line for users in group $1 — both supplementary
# members (4th /etc/group field) and primary-group members (passwd gid
# match). Linux / illumos / FreeBSD / OpenBSD use getent(1); macOS has
# no getent and stores users in Directory Services, so fall back to
# dscl(1) there. Returns silently if neither resolver is available or
# the group is unknown.
list_users_in_group() {
  local g="$1" gid
  if command -v getent >/dev/null 2>&1; then
    gid=$(getent group "$g" 2>/dev/null | awk -F: '{print $3}')
    [ -n "$gid" ] || return 0
    {
      getent group "$g" | awk -F: '{n=split($4,m,","); for(i=1;i<=n;i++) if(m[i]!="") print m[i]}'
      getent passwd | awk -F: -v gid="$gid" '$4==gid {print $1}'
    }
  elif command -v dscl >/dev/null 2>&1; then
    gid=$(dscl . -read "/Groups/$g" PrimaryGroupID 2>/dev/null \
            | awk '/^PrimaryGroupID:/ {print $2}')
    [ -n "$gid" ] || return 0
    {
      # Supplementary members. macOS records gid 20 (staff) members as
      # primary-only, so this branch is usually empty for staff but
      # populates for groups created with `dseditgroup -o edit -a`.
      dscl . -read "/Groups/$g" GroupMembership 2>/dev/null \
        | sed -n 's/^GroupMembership: //p' | tr ' ' '\n'
      # Primary members: every user whose PrimaryGroupID matches.
      dscl . -list /Users PrimaryGroupID | awk -v gid="$gid" '$2==gid {print $1}'
    } | grep -v '^$' || true
  fi
}

# Print the login shell of user $1, or nothing if unknown. Mirrors
# list_users_in_group's portability split: getent(1) where present,
# dscl(1) on macOS.
lookup_user_shell() {
  local u="$1"
  if command -v getent >/dev/null 2>&1; then
    getent passwd "$u" 2>/dev/null | awk -F: '{print $7}'
  elif command -v dscl >/dev/null 2>&1; then
    dscl . -read "/Users/$u" UserShell 2>/dev/null | sed -n 's/^UserShell: //p'
  fi
}

# Pick another invitable user on this box: a member of TMUX_PARTY_GROUP
# (supplementary or primary) other than $USER. Empty output if none.
# Uses group membership rather than a UID floor so it works across
# Linux (uid>=1000), illumos/Solaris (uid>=100), and BSD conventions.
# Excludes root and no-login system accounts (tmux server-access refuses
# to manage root, and system pseudo-users aren't sensible test peers).
pick_other_user() {
  local g="${TMUX_PARTY_GROUP:-party}"
  local candidates u shell
  candidates=$(list_users_in_group "$g")
  for u in $candidates; do
    [ "$u" = "$USER" ] && continue
    [ "$u" = "root" ] && continue
    shell=$(lookup_user_shell "$u")
    case "$shell" in
      *false|*nologin|"") continue ;;
    esac
    printf '%s\n' "$u"
    return 0
  done
}

# Source ./party in library mode for pure-function tests.
load_party_lib() {
  __PARTY_LIB_ONLY=1
  # shellcheck disable=SC1090
  . "$PARTY_BIN"
}
