#!/usr/bin/env bash
set -uo pipefail
shopt -s nullglob

# Migration en masse Fastmail -> Google Workspace, OAuth + PARALLELE.
# Sortie propre : une barre de progression par boîte en cours de migration,
# redessinée en place. Toute la verbosité (GAM, imapsync) part dans les logs.
#
#   migrer-masse-parallele.sh                 migre tous les comptes du CSV
#   migrer-masse-parallele.sh --dry           test à blanc (passé à imapsync)
#   migrer-masse-parallele.sh user@qlf.fr     ne traite que ce compte
#   JOBS=4 migrer-masse-parallele.sh          4 boîtes en parallèle (défaut: 3)
#
# Réglages surchargeables par variable d'environnement :
#   JOBS=3                boîtes simultanées
#   TWO_PASS=1            1 = petits messages d'abord puis les gros (défaut)
#   SMALL_FIRST_MAXSIZE   seuil "petit message" en octets (défaut 2000000 = 2 Mo)
#   SKIP_DONE=1           ne pas rescanner une boîte déjà terminée (défaut)
#   FORCE=1               forcer même les boîtes déjà terminées
#
# Le quota d'import IMAP de Gmail (~1 Go/jour/boîte) fait qu'une grosse boîte
# ne migre pas en une fois : le script sort alors en "QUOTA (reprise demain)".
# Il suffit de relancer chaque jour (voir migrer-cron.sh) : imapsync reprend où
# il s'était arrêté jusqu'à ce que la boîte soit "terminée".

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

CSV="${MIGRER_CSV:-migrations.csv}"
SRC_HOST="${SRC_HOST:-imap.fastmail.com}"
SRC_PORT="${SRC_PORT:-993}"
DST_HOST="${DST_HOST:-imap.gmail.com}"
DST_PORT="${DST_PORT:-993}"
SA_JSON="${SA_JSON:-$HOME/.gam/oauth2service.json}"
GAM_BIN="${GAM_BIN:-$HOME/bin/gam7/gam}"

# Boîtes simultanées. 2-4 raisonnable ; au-delà, throttling Gmail/Fastmail.
JOBS="${JOBS:-3}"

# Motifs de parsing imapsync 2.229 (surchargeables si une barre reste à 0%).
# Progression = (déjà en destination) + (copiés cette session) / (total source).
#  - total : ligne "Host1 Nb messages:   16802 messages"   (dénominateur)
#  - base  : ligne "Host2 Nb messages:   16760 messages"   (déjà migrés = départ)
#  - copié : une ligne "copied" par message réellement transféré cette session
PROGRESS_TOTAL_RE="${PROGRESS_TOTAL_RE:-Host1 Nb messages:}"
PROGRESS_BASE_RE="${PROGRESS_BASE_RE:-Host2 Nb messages:}"
PROGRESS_DONE_RE="${PROGRESS_DONE_RE:-copied}"

CONF_DIR="${HOME}/.migrer"
LOG_DIR="${CONF_DIR}/logs"
CRED_FILE="${CRED_FILE:-$SCRIPT_DIR/comptes-crees.csv}"
mkdir -p "$LOG_DIR"
[ -f "$CRED_FILE" ] || echo "email,mot_de_passe_temporaire" > "$CRED_FILE"
chmod 600 "$CRED_FILE"

ONLY=""
if [ "${1:-}" ] && [[ "$1" == *@* ]]; then ONLY="$1"; shift; fi
EXTRA=("$@")

[ -f "$CSV" ]     || { echo "CSV introuvable : $CSV" >&2; exit 1; }
[ -f "$SA_JSON" ] || { echo "Service account introuvable : $SA_JSON" >&2; exit 1; }
command -v imapsync >/dev/null || { echo "imapsync absent du PATH" >&2; exit 1; }

# Répertoire d'état partagé entre le moteur d'affichage et les workers.
STATE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/migrer.XXXXXX")"

BATCH_PID=""
cleanup() { printf '\033[?25h'; [ -n "$BATCH_PID" ] && kill "$BATCH_PID" 2>/dev/null; rm -rf "$STATE_DIR"; }
trap cleanup EXIT
trap 'exit 130' INT TERM

# ---------------------------------------------------------------------------
# Extraction de progression depuis le logfile imapsync d'une boîte
# ---------------------------------------------------------------------------
# Extrait le nombre en fin de la 1re ligne correspondant au motif ($2) du log ($1)
_count_field() {
  local lf="$1" re="$2"
  [ -n "$lf" ] && [ -f "$lf" ] || return
  grep -aoE "${re}[^0-9]*[0-9]+" "$lf" 2>/dev/null | head -1 | grep -oE '[0-9]+$'
}
get_total() { _count_field "$1" "$PROGRESS_TOTAL_RE"; }   # total source (dénominateur)
get_base()  { _count_field "$1" "$PROGRESS_BASE_RE"; }    # déjà en destination (départ)
get_copied() {   # -> messages réellement copiés cette session
  local lf="$1"
  [ -n "$lf" ] && [ -f "$lf" ] || { printf 0; return; }
  local c; c="$(grep -acE "$PROGRESS_DONE_RE" "$lf" 2>/dev/null)"
  printf '%s' "${c:-0}"
}

# ---------------------------------------------------------------------------
# Worker : traite UNE boîte (création GAM si besoin + imapsync).
# Toute la sortie part dans un log de run. L'état est publié dans STATE_DIR.
# ---------------------------------------------------------------------------
worker() {
  local seq="$1" DEST="$2" SRC_LOGIN="$3" SRC_PASS="$4"
  local safe="${DEST//\//_}"
  local base; base="$(printf '%s/box.%04d' "$STATE_DIR" "$seq")"
  local stamp; stamp="$(date +%Y%m%d_%H%M%S)"
  local runlog="$LOG_DIR/${safe}_${stamp}.run.log"

  printf '%s' "$DEST"   > "$base.name"
  printf 'create'       > "$base.phase"

  {
    # 1. compte : créer si absent
    if "$GAM_BIN" info user "$DEST" >/dev/null 2>&1; then
      echo "compte déjà présent"
    else
      local local_part="${DEST%@*}" domain_part="${DEST#*@}"
      local TEMP_PW; TEMP_PW="$(head -c 16 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | cut -c1-14)"
      if ! "$GAM_BIN" create user "$DEST" \
          firstname "$local_part" lastname "$domain_part" \
          password "$TEMP_PW" changepassword on; then
        printf 'KO (création)' > "$base.done"
        return 1
      fi
      echo "${DEST},${TEMP_PW}" >> "$CRED_FILE"   # append 1 ligne = atomique
      sleep 10
    fi

    # 2. migration imapsync
    printf 'run' > "$base.phase"

    # Boîte déjà terminée un jour précédent (passe complète sortie en code 0) ?
    # On ne la rescanne pas chaque nuit. FORCE=1 pour refaire quand même.
    if [ "${SKIP_DONE:-1}" = "1" ] && [ -z "${FORCE:-}" ]; then
      local last
      last="$(ls -t "$LOG_DIR/${safe}"_*.p2-full.log 2>/dev/null | head -1)"
      if [ -n "$last" ] && grep -aq 'return value 0 (EX_OK' "$last"; then
        printf 'OK (déjà terminé)' > "$base.done"
        return 0
      fi
    fi

    # Une passe imapsync. $1 = suffixe de log ; le reste = options en plus.
    _imapsync_pass() {
      local suffix="$1"; shift
      local log="${safe}_${stamp}${suffix}.log"
      printf '%s' "$LOG_DIR/$log" > "$base.log"   # la barre suit ce log
      imapsync \
        --host1 "$SRC_HOST" --port1 "$SRC_PORT" --ssl1 \
        --user1 "$SRC_LOGIN" --password1 "$SRC_PASS" \
        --host2 "$DST_HOST" --port2 "$DST_PORT" --ssl2 \
        --user2 "$DEST" --password2 "$SA_JSON" --authmech2 XOAUTH2 \
        --automap --skipcrossduplicates --addheader \
        --regextrans2 's/\s+$//' --regextrans2 's/\s+\//\//g' \
        --logdir "$LOG_DIR" --logfile "$log" \
        "$@" ${EXTRA[@]+"${EXTRA[@]}"}
    }

    # Passe 1 (optionnelle) : les PETITS messages d'abord. Gmail bride l'import
    # IMAP à ~1 Go/jour/boîte ; en poussant d'abord les mails légers on en migre
    # un maximum avant d'épuiser le quota sur 2-3 pièces jointes de 18 Mo, et on
    # ne gaspille pas le budget d'erreurs (--errorsmax) sur les gros.
    if [ "${TWO_PASS:-1}" = "1" ]; then
      _imapsync_pass ".p1-small" --maxsize "${SMALL_FIRST_MAXSIZE:-2000000}"
    fi

    # Passe 2 : tout le reste (gros messages compris). C'est elle qui fait foi.
    local rc
    _imapsync_pass ".p2-full"; rc=$?
    case "$rc" in
      0)   printf 'OK'                     > "$base.done" ;;
      113) printf 'QUOTA (reprise demain)' > "$base.done"; return 2 ;;
      *)   printf 'KO (migration)'         > "$base.done"; return 1 ;;
    esac
  } > "$runlog" 2>&1
}

# ---------------------------------------------------------------------------
# Pool : lance les workers en limitant à $JOBS actifs (tourne en arrière-plan)
# ---------------------------------------------------------------------------
run_batch() {
  local seq=0 running=0
  while IFS=, read -r DEST SRC_LOGIN SRC_PASS; do
    DEST="$(echo "$DEST" | tr -d '\r' | xargs)"
    SRC_LOGIN="$(echo "$SRC_LOGIN" | tr -d '\r' | xargs)"
    SRC_PASS="$(echo "$SRC_PASS" | tr -d '\r' | xargs)"
    [ -z "$DEST" ] && continue
    [ -n "$ONLY" ] && [ "$DEST" != "$ONLY" ] && continue

    worker "$seq" "$DEST" "$SRC_LOGIN" "$SRC_PASS" &
    seq=$((seq+1)); running=$((running+1))
    if [ "$running" -ge "$JOBS" ]; then wait -n; running=$((running-1)); fi
  done < <(tail -n +2 "$CSV")
  wait
}

# ---------------------------------------------------------------------------
# Rendu d'une barre pour une boîte
# ---------------------------------------------------------------------------
make_bar() {
  local name="$1" done="$2" total="$3" sp="$4" width=20
  if [ -z "$total" ] || [ "$total" -le 0 ] 2>/dev/null; then
    printf '%-30s [%*s] %s analyse des dossiers…' "$name" "$width" '' "$sp"
    return
  fi
  [ "$done" -gt "$total" ] && done="$total"
  local pct=$(( done * 100 / total ))
  local filled=$(( done * width / total ))
  local bar
  bar="$(printf '%*s' "$filled" '' | tr ' ' '#')"
  bar+="$(printf '%*s' $((width - filled)) '' | tr ' ' '-')"
  printf '%-30s [%s] %3d%%  (%d/%d)' "$name" "$bar" "$pct" "$done" "$total"
}

# Construit le tableau CUR_LINES des boîtes actuellement actives (triées par seq)
PREV=0
TICK=0
SPIN=('|' '/' '-' '\')
collect_lines() {
  CUR_LINES=()
  local sp="${SPIN[$((TICK % 4))]}"
  local f base name phase lf total start copied done
  for f in "$STATE_DIR"/box.*.name; do
    base="${f%.name}"
    [ -f "$base.done" ] && continue        # terminée -> plus active
    name="$(cat "$base.name" 2>/dev/null)"
    [ -z "$name" ] && continue
    phase="$(cat "$base.phase" 2>/dev/null)"
    if [ "$phase" = "run" ]; then
      lf="$(cat "$base.log" 2>/dev/null)"
      total="$(get_total "$lf")"
      start="$(get_base "$lf")"; [ -z "$start" ] && start=0
      copied="$(get_copied "$lf")"
      done=$((start + copied))     # déjà migrés + copiés cette session
      CUR_LINES+=("$(make_bar "$name" "$done" "$total" "$sp")")
    else
      CUR_LINES+=("$(printf '%-30s %s création du compte…' "$name" "$sp")")
    fi
  done
}

# Redessine en place (efface l'ancien bloc, écrit le nouveau)
draw() {
  local n=${#CUR_LINES[@]} i
  [ "$PREV" -gt 0 ] && printf '\033[%dA' "$PREV"
  for ((i=0; i<n; i++)); do printf '\033[2K%s\n' "${CUR_LINES[i]}"; done
  if [ "$PREV" -gt "$n" ]; then
    local extra=$((PREV - n))
    for ((i=0; i<extra; i++)); do printf '\033[2K\n'; done
    printf '\033[%dA' "$extra"
  fi
  PREV=$n
}

# ---------------------------------------------------------------------------
# Lancement
# ---------------------------------------------------------------------------
run_batch & BATCH_PID=$!

if [ -t 1 ]; then
  printf '\033[?25l'                         # cache le curseur
  while kill -0 "$BATCH_PID" 2>/dev/null; do
    collect_lines; draw; TICK=$((TICK + 1)); sleep 1
  done
  collect_lines; draw                         # dernière frame (bloc vide -> nettoyé)
  printf '\033[?25h'
else
  wait "$BATCH_PID"                            # pas de TTY : pas de barres
fi
wait "$BATCH_PID" 2>/dev/null

# ---------------------------------------------------------------------------
# Synthèse finale
# ---------------------------------------------------------------------------
ok=(); quota=(); ko=()
for f in "$STATE_DIR"/box.*.done; do
  base="${f%.done}"; name="$(cat "$base.name" 2>/dev/null)"
  if   grep -q '^OK'    "$f"; then ok+=("$name")
  elif grep -q '^QUOTA' "$f"; then quota+=("$name")
  else ko+=("$name ($(cat "$f"))"); fi
done

echo "Réussis : ${#ok[@]}    Quota (reprise demain) : ${#quota[@]}    Échecs : ${#ko[@]}"
[ "${#quota[@]}" -gt 0 ] && printf '  QUOTA: %s\n' "${quota[@]}"
[ "${#ko[@]}"    -gt 0 ] && printf '  KO: %s\n'    "${ko[@]}"
