#!/usr/bin/env bash
#
# Push the contents of a git repo (or subdir) into a Kubernetes container
# using `git archive` piped to `kubectl exec tar -x`.
#
# Supports atomic swap:
#   --atomic rename   : extract to temp dir, then mv into place
#   --atomic symlink  : extract to releases/<timestamp>, then update symlink at DEST
#
# Requirements:
#   - kubectl access to the target pod/container
#   - 'sh' and 'tar' installed in the container (busybox tar is fine)
#   - run from inside a Git repository
#
# Usage:
#   ./push-archive-to-pod.sh \
#     --pod my-pod \
#     --namespace default \
#     --container app \
#     --dest /app \
#     --ref main \
#     [--path subdir/] \
#     [--strip-prefix] \
#     [--atomic rename|symlink] \
#     [--force-symlink-convert] \
#     [--releases-dir /app.releases] \
#     [--keep-old] \
#     [--owner appuser] \
#     [--group appgroup] \
#     [--mode 755]
#
set -euo pipefail

# ---- Defaults ----
NAMESPACE=""
CONTAINER=""
POD=""
DEST=""
REF="HEAD"
PATHSPEC=""
STRIP_PREFIX="false"
OWNER=""
GROUP=""
MODE=""
ATOMIC=""
FORCE_SYMLINK_CONVERT="false"
RELEASES_DIR=""
KEEP_OLD="false"

# ---- Helpers ----
usage() {
  cat <<'EOF'
Usage:
  push-archive-to-pod.sh --pod <pod> --dest </path/in/container> [options]

Required:
  --pod            Pod name
  --dest           Destination directory inside the container

Optional:
  --namespace      Kubernetes namespace (default: current kubectl context ns)
  --container      Container name (if multiple containers in pod)
  --ref            Git ref/branch/tag/commit to archive (default: HEAD)
  --path           Limit archive to a subdirectory (e.g., "web/")
  --strip-prefix   Strip the leading directory from the archive entries (only meaningful with --path ending in '/')
  --atomic         Atomic swap mode: "rename" or "symlink"
  --force-symlink-convert  On first use of --atomic symlink, convert DEST from dir to symlink
  --releases-dir   Where to store releases for symlink mode (default: DEST.releases)
  --keep-old       Keep previous directory/symlink target for rollback (default: delete old)
  --owner          chown recursively after extract (e.g., "appuser")
  --group          chgrp recursively after extract (e.g., "appgroup")
  --mode           chmod recursively after extract (e.g., "755")

Notes:
  - Run from the root of your Git repo. If using --path, end with a slash for directory.
  - --strip-prefix uses "tar --strip-components=1" in the container and requires --path to end with '/'.
  - Atomic rename performs two renames inside the same filesystem; the switch itself is fast.
  - Atomic symlink makes DEST a symlink and updates it to point to the new release directory.
EOF
}

die() { echo "ERROR: $*" >&2; exit 1; }

# ---- Parse args ----
while [[ $# -gt 0 ]]; do
  case "$1" in
    --pod) POD="$2"; shift 2;;
    --namespace) NAMESPACE="$2"; shift 2;;
    --container) CONTAINER="$2"; shift 2;;
    --dest) DEST="$2"; shift 2;;
    --ref) REF="$2"; shift 2;;
    --path) PATHSPEC="$2"; shift 2;;
    --strip-prefix) STRIP_PREFIX="true"; shift 1;;
    --owner) OWNER="$2"; shift 2;;
    --group) GROUP="$2"; shift 2;;
    --mode) MODE="$2"; shift 2;;
    --atomic) ATOMIC="$2"; shift 2;;
    --force-symlink-convert) FORCE_SYMLINK_CONVERT="true"; shift 1;;
    --releases-dir) RELEASES_DIR="$2"; shift 2;;
    --keep-old) KEEP_OLD="true"; shift 1;;
    -h|--help) usage; exit 0;;
    *) die "Unknown argument: $1";;
  esac
done

[[ -n "$POD" ]] || die "--pod is required"
[[ -n "$DEST" ]] || die "--dest is required"

# Validate Git repo context
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "Not inside a Git repository"

# Resolve namespace & container flags
NS_FLAG=()
[[ -n "$NAMESPACE" ]] && NS_FLAG=( -n "$NAMESPACE" )
C_FLAG=()
[[ -n "$CONTAINER" ]] && C_FLAG=( -c "$CONTAINER" )

# Determine releases dir for symlink mode
if [[ -z "$RELEASES_DIR" ]]; then
  RELEASES_DIR="${DEST}.releases"
fi

# ---- Prepare container paths based on atomic mode ----
TS=$(date +%Y%m%d-%H%M%S)
NEW_DIR=""         # Where to extract in container
SWAP_PLAN=""       # Description for logging

case "$ATOMIC" in
  "" )
    SWAP_PLAN="non-atomic: direct extract into $DEST"
    NEW_DIR="$DEST"
    ;;
  rename )
    SWAP_PLAN="atomic rename: extract to ${DEST}.__new-$TS then mv into place"
    NEW_DIR="${DEST}.__new-$TS"
    ;;
  symlink )
    SWAP_PLAN="atomic symlink: extract to ${RELEASES_DIR}/$TS then update symlink $DEST -> ${RELEASES_DIR}/$TS"
    NEW_DIR="${RELEASES_DIR}/$TS"
    ;;
  * )
    die "--atomic must be 'rename' or 'symlink' (or omitted)"
    ;;
esac

echo "[+] Mode: $SWAP_PLAN"

# Ensure necessary directories exist (container side)
if [[ "$ATOMIC" == "symlink" ]]; then
  echo "[+] Ensuring releases dir exists: $RELEASES_DIR"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "mkdir -p '$RELEASES_DIR'"
else
  echo "[+] Ensuring destination parent exists: $(dirname "$DEST")"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "mkdir -p '$(dirname "$DEST")'"
fi

# Create NEW_DIR when needed
if [[ "$ATOMIC" == "rename" || "$ATOMIC" == "symlink" ]]; then
  echo "[+] Creating new extract dir: $NEW_DIR"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "rm -rf '$NEW_DIR' && mkdir -p '$NEW_DIR'"
else
  echo "[+] Ensuring destination dir exists: $DEST"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "mkdir -p '$DEST'"
fi

# ---- Build git archive command ----
ARCHIVE_CMD=( git archive --format=tar "$REF" )
if [[ -n "$PATHSPEC" ]]; then
  ARCHIVE_CMD+=( "$PATHSPEC" )
fi

# Strip prefix handling
TAR_EXTRACT_OPTS=()
if [[ "$STRIP_PREFIX" == "true" ]]; then
  if [[ -n "$PATHSPEC" && "$PATHSPEC" == */ ]]; then
    TAR_EXTRACT_OPTS=( "--strip-components=1" )
  else
    echo "WARNING: --strip-prefix requested but --path does not end with '/'. Ignoring."
  fi
fi

EXTRACT_CMD="tar -x${TAR_EXTRACT_OPTS:+ $(printf '%s ' "${TAR_EXTRACT_OPTS[@]}")} -C '$NEW_DIR'"

echo "[+] Archiving ref '$REF' ${PATHSPEC:+(path: $PATHSPEC)}"
echo "    -> Streaming into pod '$POD' ${NAMESPACE:+(ns: $NAMESPACE)} ${CONTAINER:+(container: $CONTAINER)}"
echo "    -> Extracting with: $EXTRACT_CMD"

# ---- Stream archive to container ----
set -o pipefail
"${ARCHIVE_CMD[@]}" \
  | kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "$EXTRACT_CMD"

echo "[+] Files extracted to $NEW_DIR"

# ---- Post-processing on NEW_DIR (ownership/permissions) ----
if [[ -n "$OWNER" ]]; then
  echo "[+] Setting owner on new dir: $OWNER"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "chown -R '$OWNER' '$NEW_DIR' || true"
fi
if [[ -n "$GROUP" ]]; then
  echo "[+] Setting group on new dir: $GROUP"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "chgrp -R '$GROUP' '$NEW_DIR' || true"
fi
if [[ -n "$MODE" ]]; then
  echo "[+] Setting mode on new dir: $MODE"
  kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "chmod -R '$MODE' '$NEW_DIR' || true"
fi

# ---- Atomic swap steps ----
case "$ATOMIC" in
  "" )
    echo "[✓] Non-atomic deploy completed to $DEST"
    ;;
  rename )
    echo "[+] Performing atomic rename swap"
    # Keep or remove old
    if [[ "$KEEP_OLD" == "true" ]]; then
      CLEAN_OLD_CMD="true"
    else
      CLEAN_OLD_CMD="rm -rf '${DEST}.__old' || true"
    fi
    kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "
      set -e
      ${CLEAN_OLD_CMD}
      # Move any existing DEST to __old
      if [ -e '$DEST' ]; then
        mv '$DEST' '${DEST}.__old'
      fi
      # Move new into place (single rename within the same filesystem)
      mv '$NEW_DIR' '$DEST'
      sync
    "
    echo "[✓] Atomic rename swap completed -> $DEST"
    ;;
  symlink )
    echo "[+] Performing atomic symlink switch"
    # If DEST is not a symlink and doesn't point to releases, optionally convert
    CONVERT_CMD=""
    if [[ "$FORCE_SYMLINK_CONVERT" == "true" ]]; then
      # Move any existing DEST into releases as an initial release, then make DEST a symlink
      CONVERT_CMD="
        if [ -d '$DEST' ] && [ ! -L '$DEST' ]; then
          INIT_REL='${RELEASES_DIR}/initial-$TS'
          rm -rf \"\$INIT_REL\" || true
          mv '$DEST' \"\$INIT_REL\"
          ln -sfn \"\$INIT_REL\" '$DEST'
        fi
      "
    fi
    # Keep or remove old symlink target
    CLEAN_OLD_CMD=""
    if [[ "$KEEP_OLD" != "true" ]]; then
      CLEAN_OLD_CMD="
        OLD_TARGET=\$(readlink '$DEST' 2>/dev/null || true)
        if [ -n \"\$OLD_TARGET\" ] && [ -e \"\$OLD_TARGET\" ]; then
          # Remove only if inside RELEASES_DIR
          case \"\$OLD_TARGET\" in
            '${RELEASES_DIR}'/*) rm -rf \"\$OLD_TARGET\" ;;
            *) true ;;
          esac
        fi
      "
    fi
    kubectl exec "${NS_FLAG[@]}" "${C_FLAG[@]}" "$POD" -- sh -lc "
      set -e
      mkdir -p '${RELEASES_DIR}'
      ${CONVERT_CMD}
      # Atomically point symlink to new release
      ln -sfn '${NEW_DIR}' '${DEST}'
      ${CLEAN_OLD_CMD}
      sync
    "
    echo "[✓] Atomic symlink switch completed -> $DEST -> $NEW_DIR"
    ;;
esac

echo "[✓] Done."

