#!/usr/bin/env bash
# git-deploy <remote> <branche...> -m "message"
#
# Depuis la branche de dev : commit (message obligatoire s'il y a des modifs)
# → merge dans une ou plusieurs branches d'env → push (dernière étape, atomique)
# → retour dev.
#
# Sécurité : toutes les mutations locales sont faites AVANT tout push.
# En cas d'échec local, l'arbre est restauré à l'identique et rien n'est poussé.
#
# Config git-native :
#   git config deploy.source <branche-dev>   (défaut : master)
#   git config deploy.push   true|false      (défaut : false — push désactivé)
set -euo pipefail

DIR=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)
# shellcheck source=_deploy_lib.sh
. "$DIR/_deploy_lib.sh"

usage() { echo 'Usage : git deploy <remote> <branche...> -m "message"'; }

SOURCE=$(deploy_source)
PUSH=$(deploy_push)

MSG=""; MSG_SET=0; POS=()
while [ $# -gt 0 ]; do
  case "$1" in
    -m|--message) MSG="${2:-}"; MSG_SET=1; shift 2 ;;
    -h|--help)    usage; exit 0 ;;
    -*)           usage; die "Option inconnue : $1" ;;
    *)            POS+=("$1"); shift ;;
  esac
done
REMOTE="${POS[0]:-}"
BRANCHES=("${POS[@]:1}")
{ [ -n "$REMOTE" ] && [ "${#BRANCHES[@]}" -ge 1 ]; } || { usage; exit 1; }

# ---- Validations pré-vol (aucune mutation) ---------------------------------
git remote get-url "$REMOTE" >/dev/null 2>&1 || die "Remote inconnu : $REMOTE"
seen=""
for b in "${BRANCHES[@]}"; do
  git show-ref --verify --quiet "refs/heads/$b" || die "Branche locale inconnue : $b"
  case " $seen " in *" $b "*) die "Branche indiquée en double : $b" ;; esac
  seen="$seen $b"
done
# '$SOURCE' est autorisée dans la liste : elle sera poussée telle quelle (pas de merge).
current=$(git branch --show-current)
[ "$current" = "$SOURCE" ] || die "Place-toi sur '$SOURCE' pour déployer (git switch $SOURCE)."
assert_clean_git_state

dirty=0; [ -n "$(git status --porcelain)" ] && dirty=1
if [ "$dirty" = 1 ]; then
  { [ "$MSG_SET" = 1 ] && [ -n "$MSG" ]; } || die 'Des modifs sont présentes : message requis (-m "…").'
fi

# ---- Confirmation ----------------------------------------------------------
list="${BRANCHES[*]}"
# branches réellement fusionnées = toutes sauf SOURCE (qui est poussée telle quelle)
merge_list=""
for b in "${BRANCHES[@]}"; do [ "$b" != "$SOURCE" ] && merge_list="$merge_list $b"; done
merge_list="${merge_list# }"
if [ "$PUSH" = true ]; then push_line="OUI vers '$REMOTE' ($list, atomique)"; else push_line="NON (désactivé)"; fi
[ "$dirty" = 1 ] && commit_line="commit sur '$SOURCE' : \"$MSG\"" || commit_line="aucun commit (arbre propre)"
[ -n "$merge_list" ] && merge_line="merge '$SOURCE' → $merge_list" || merge_line="aucun merge (push direct)"
confirm "  • $commit_line
  • $merge_line
  • push : $push_line"

# ---- Sauvegarde de l'état pour rollback ------------------------------------
SAVED_SOURCE=$(git rev-parse --verify "refs/heads/$SOURCE")
SAVED_BRANCH=()
for b in "${BRANCHES[@]}"; do SAVED_BRANCH+=("$(git rev-parse --verify "refs/heads/$b")"); done
COMMITTED=0

rollback() {
  set +e
  echo "↩ Restauration de l'état initial…" >&2
  local gd i; gd=$(git rev-parse --git-dir)
  [ -e "$gd/MERGE_HEAD" ] && git merge --abort
  git reset --hard -q
  for i in "${!BRANCHES[@]}"; do
    git update-ref "refs/heads/${BRANCHES[$i]}" "${SAVED_BRANCH[$i]}"
  done
  git switch -q "$SOURCE" 2>/dev/null || git switch -q --force "$SOURCE"
  if [ "$COMMITTED" = 1 ]; then
    # défait le commit mais garde les fichiers modifiés (état d'origine)
    git reset --mixed -q "$SAVED_SOURCE"
  else
    git reset --hard -q "$SAVED_SOURCE"
  fi
  echo "✓ État initial restauré. Aucun push effectué." >&2
}
fail() { echo "✗ $1" >&2; rollback; exit 1; }

# ---- Mutations locales (aucun réseau) --------------------------------------
if [ "$dirty" = 1 ]; then
  git add -A
  git commit -q -m "$MSG" || fail "Échec du commit sur '$SOURCE'."
  COMMITTED=1
  echo "✓ Commit sur '$SOURCE' : $MSG"
fi

for b in "${BRANCHES[@]}"; do
  if [ "$b" = "$SOURCE" ]; then
    echo "✓ '$b' (dev) incluse au push telle quelle."
    continue
  fi
  git switch -q "$b" || fail "Impossible de basculer sur '$b'."
  git merge --no-edit -q "$SOURCE" || fail "Conflit de merge sur '$b'."
  echo "✓ '$b' à jour depuis '$SOURCE'."
done
git switch -q "$SOURCE" || fail "Impossible de revenir sur '$SOURCE'."
echo "✓ Local OK : $list."

# ---- Push : dernière étape, atomique, jamais forcé -------------------------
if [ "$PUSH" = true ]; then
  echo "→ Vérification du push (dry-run)…"
  git push --atomic --dry-run "$REMOTE" "${BRANCHES[@]}" \
    || { echo "✗ Push rejeté au dry-run (non fast-forward ?). RIEN poussé. Local conservé." >&2; exit 1; }
  echo "→ Push atomique ($list) → '$REMOTE'…"
  git push --atomic "$REMOTE" "${BRANCHES[@]}" \
    || { echo "✗ Échec du push. RIEN poussé (atomique). Local conservé." >&2; \
         echo "  Relance : git push --atomic $REMOTE $list" >&2; exit 1; }
  echo "✓ Poussé sur '$REMOTE'."
else
  echo "→ (push désactivé) aurait fait : git push --atomic $REMOTE $list"
fi
echo "✓ Terminé. Tu es sur '$SOURCE'."
