#!/usr/bin/env bash
# ouvrir_campagne_staging.sh — l'enveloppe de la cérémonie : ouvre la FENÊTRE de
# force-push sur `staging`, joue `scripts/recreer_staging.sh`, REFERME TOUJOURS.
#
# POURQUOI PAS `allowsForcePushes: true` EN PERMANENCE
#   La liste de raccordement de la cérémonie (PR #662) demandait d'« autoriser
#   le force-push » dans la protection. Mesuré le 25/08 avant de le faire :
#   SCHOTTL et Abakan (équipe de développement externe) ont `push: true` sur les
#   DEUX dépôts. Un force-push ouvert en permanence = n'importe quel compte à
#   écriture peut réécrire la branche de recette EN SILENCE, hors cérémonie,
#   sans tag d'archive ni verrou de report. La fenêtre, elle, dure le temps de
#   la cérémonie, dans une enveloppe qui la referme MÊME EN CAS D'ÉCHEC (trap)
#   et VÉRIFIE l'état final. Bascule par API mesurée possible le 25/08 (dans
#   les deux sens, sur règle existante). Condition posée à l'arbitrage
#   d'Enguerran du 25/08 (plan 26_PLAN_MODELE_RELEASE.md, v2).
#
# USAGE — depuis le clone du dépôt concerné, comme la cérémonie elle-même :
#   ./scripts/ouvrir_campagne_staging.sh <arguments passés à recreer_staging.sh>
#   ./scripts/ouvrir_campagne_staging.sh --auto-test   # banc hors ligne, 5 scénarios
#
# CE QUE L'ENVELOPPE NE FAIT PAS : elle ne rejoue aucun verrou de la cérémonie
# (report, tag, PR ouvertes) — c'est le travail de recreer_staging.sh, qui
# refuse seul. Elle ne gère QUE la fenêtre de protection.
#
# AUTOTEST — le pattern vient du banc « GitHub factice » écrit par la session
# bascule (commit 9f1bb9a, retiré de #662 quand la répartition cérémonie/
# enveloppe a été figée — repris ici, où la fenêtre vit désormais) : les deux
# primitives de la fenêtre sont INJECTABLES, l'état tient dans un fichier, et
# les cinq scénarios se jouent hors ligne, sans un appel GitHub. Seule la
# mutation GraphQL réelle reste improuvable hors ligne — elle a été mesurée le
# 25/08 (bascule dans les deux sens sur règle existante) et sera exercée par la
# première cérémonie réelle.

set -euo pipefail

rouge()  { printf '\033[31m%s\033[0m\n' "$*" >&2; }
vert()   { printf '\033[32m%s\033[0m\n' "$*"; }

# ─── Les deux primitives de la fenêtre — INJECTABLES pour l'autotest ─────────
# En mode réel : GraphQL sur la règle de protection au motif `staging`.
# En mode factice (ENVELOPPE_FACTICE_ETAT défini) : l'état vit dans un fichier,
# et ENVELOPPE_FACTICE_MODE simule les pannes (refus-fermeture, ouverture-
# inefficace). AUCUN comportement réel n'est modifié quand la variable est
# absente.

lire_force_push() {
  if [ -n "${ENVELOPPE_FACTICE_ETAT:-}" ]; then cat "$ENVELOPPE_FACTICE_ETAT"; return; fi
  gh api graphql \
    -f query='query($o:String!,$n:String!){ repository(owner:$o,name:$n){ branchProtectionRules(first:20){ nodes { pattern allowsForcePushes } } } }' \
    -f o="$PROPRIO" -f n="$NOM" \
    --jq '.data.repository.branchProtectionRules.nodes[] | select(.pattern=="staging") | .allowsForcePushes'
}

bascule() { # $1 = true|false ; rend l'état résultant annoncé par l'API
  if [ -n "${ENVELOPPE_FACTICE_ETAT:-}" ]; then
    case "${ENVELOPPE_FACTICE_MODE:-nominal}" in
      refus-fermeture)      [ "$1" = "false" ] && { echo "erreur-factice"; return 1; } ;;
      ouverture-inefficace) [ "$1" = "true" ]  && { echo "false"; return 0; } ;;
    esac
    echo "$1" > "$ENVELOPPE_FACTICE_ETAT"; echo "$1"; return 0
  fi
  gh api graphql \
    -f query='mutation($id:ID!,$v:Boolean!){ updateBranchProtectionRule(input:{branchProtectionRuleId:$id, allowsForcePushes:$v}){ branchProtectionRule { allowsForcePushes } } }' \
    -f id="$REGLE" -F v="$1" --jq '.data.updateBranchProtectionRule.branchProtectionRule.allowsForcePushes'
}

resoudre_contexte() {
  if [ -n "${ENVELOPPE_FACTICE_ETAT:-}" ]; then
    DEPOT="factice/FACTICE"; PROPRIO=factice; NOM=FACTICE
    REGLE="${ENVELOPPE_FACTICE_REGLE:-BPR_factice}"
    return
  fi
  DEPOT=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null) || {
    rouge "REFUS — impossible de déterminer le dépôt courant ('gh repo view')."; exit 2; }
  PROPRIO=${DEPOT%%/*}; NOM=${DEPOT##*/}
  REGLE=$(gh api graphql \
    -f query='query($o:String!,$n:String!){ repository(owner:$o,name:$n){ branchProtectionRules(first:20){ nodes { id pattern } } } }' \
    -f o="$PROPRIO" -f n="$NOM" \
    --jq '.data.repository.branchProtectionRules.nodes[] | select(.pattern=="staging") | .id')
}

# ─── La séquence réelle ──────────────────────────────────────────────────────
campagne() {
  resoudre_contexte
  [ -n "$REGLE" ] || { rouge "REFUS — aucune règle de protection au motif 'staging' sur $DEPOT."; exit 2; }

  # La refermeture est INCONDITIONNELLE : succès, échec, interruption — le trap
  # joue dans tous les cas, et l'état final est VÉRIFIÉ par relecture. Si la
  # refermeture elle-même échoue, on sort en erreur avec l'instruction exacte :
  # une fenêtre restée ouverte est un trou, pas un détail.
  refermer() {
    code=$?
    bascule false >/dev/null 2>&1 || true
    relu=$(lire_force_push 2>/dev/null) || relu="(échec de lecture)"
    if [ "$relu" != "false" ]; then
      rouge "⚠️  LA FENÊTRE N'EST PAS REFERMÉE (allowsForcePushes=$relu sur $DEPOT)."
      rouge "    Refermer À LA MAIN, tout de suite :"
      rouge "    gh api graphql -f query='mutation{ updateBranchProtectionRule(input:{branchProtectionRuleId:\"$REGLE\", allowsForcePushes:false}){ clientMutationId } }'"
      exit 3
    fi
    vert "fenêtre refermée et VÉRIFIÉE (allowsForcePushes=false)."
    exit "$code"
  }
  trap refermer EXIT INT TERM

  avant=$(lire_force_push)
  [ "$avant" = "false" ] || rouge "⚠️  la fenêtre était DÉJÀ ouverte avant la cérémonie (allowsForcePushes=$avant) — anomalie à signaler, on continue et on refermera."

  vert "ouverture de la fenêtre de force-push sur $DEPOT (staging)…"
  resultat=$(bascule true) || resultat=""
  [ "$resultat" = "true" ] || { rouge "REFUS — l'ouverture de la fenêtre a échoué (état annoncé : ${resultat:-aucun})."; exit 2; }

  "${ENVELOPPE_CEREMONIE:-$(dirname "$0")/recreer_staging.sh}" "$@"
}

# ─── L'autotest — 5 scénarios hors ligne, dont 4 pannes ──────────────────────
autotest() {
  T=$(mktemp -d); VERTS=0; TOTAL=0
  echo "exit 0"  > "$T/ceremonie_ok.sh";     chmod +x "$T/ceremonie_ok.sh"
  echo "exit 2"  > "$T/ceremonie_refus.sh";  chmod +x "$T/ceremonie_refus.sh"
  # la cérémonie témoin note l'état de la fenêtre au moment où elle tourne
  printf '%s\n' '#!/usr/bin/env bash' 'cat "$ENVELOPPE_FACTICE_ETAT" > "$ENVELOPPE_TEMOIN"; exit 0' > "$T/ceremonie_temoin.sh"
  chmod +x "$T/ceremonie_temoin.sh"

  jouer() { # nom attendu_code mode ceremonie [verif_supplementaire]
    TOTAL=$((TOTAL+1)); nom=$1; attendu=$2; mode=$3; cer=$4
    echo "false" > "$T/etat"
    code=0
    ENVELOPPE_FACTICE_ETAT="$T/etat" ENVELOPPE_FACTICE_MODE="$mode" \
      ENVELOPPE_CEREMONIE="$cer" ENVELOPPE_TEMOIN="$T/temoin" \
      bash "$0" >/dev/null 2>&1 || code=$?
    final=$(cat "$T/etat")
    pb=""
    [ "$code" = "$attendu" ] || pb="code=$code attendu=$attendu"
    if [ "$mode" != "refus-fermeture" ] && [ "$final" != "false" ]; then pb="$pb état_final=$final"; fi
    if [ -n "$pb" ]; then rouge "  ✗ S$TOTAL $nom — $pb"; else VERTS=$((VERTS+1)); vert "  ✓ S$TOTAL $nom"; fi
  }

  jouer "nominal : ouvre, cérémonie OK, referme"            0 nominal              "$T/ceremonie_ok.sh"
  # la fenêtre était bien OUVERTE pendant la cérémonie ?
  TOTAL=$((TOTAL+1))
  echo "false" > "$T/etat"; : > "$T/temoin"
  ENVELOPPE_FACTICE_ETAT="$T/etat" ENVELOPPE_FACTICE_MODE=nominal \
    ENVELOPPE_CEREMONIE="$T/ceremonie_temoin.sh" ENVELOPPE_TEMOIN="$T/temoin" \
    bash "$0" >/dev/null 2>&1 || true
  if [ "$(cat "$T/temoin")" = "true" ] && [ "$(cat "$T/etat")" = "false" ]; then
    VERTS=$((VERTS+1)); vert "  ✓ S$TOTAL la cérémonie a VU la fenêtre ouverte, et elle est refermée après"
  else rouge "  ✗ S$TOTAL témoin=$(cat "$T/temoin") final=$(cat "$T/etat")"; fi
  jouer "la cérémonie REFUSE : referme quand même, code propagé" 2 nominal          "$T/ceremonie_refus.sh"
  jouer "ouverture INEFFICACE : refus AVANT la cérémonie"        2 ouverture-inefficace "$T/ceremonie_ok.sh"
  jouer "refermeture en PANNE : sortie 3, jamais un silence"     3 refus-fermeture  "$T/ceremonie_ok.sh"

  echo; echo "$VERTS/$TOTAL scénarios verts."
  rm -rf "$T"
  [ "$VERTS" = "$TOTAL" ] || exit 1
}

if [ "${1:-}" = "--auto-test" ]; then autotest; else campagne "$@"; fi
