#!/usr/bin/env bash
# bind_mount.sh — Bindfs-only mounts from 7-column config
# -----------------------------------------------------------------------------
# Config (| separator, 7 columns):
#   src | dest | presented_uid | presented_gid | view_perms | applied_user | create_file_perms
#
# Options:
#   --config=PATH            (default: /opt/scripts_local/bind_mount.conf)
#   --dry-run                Show actions only
#   --verbose, -v            Verbose logs
#   --no-create-src          Do not create missing source directories
#   --force-unmount=defined  Unmount bindfs mounts defined in config
#   --force-unmount=all      Unmount ALL bindfs (fuse.bindfs) on system
#   --replace-conflicts      If DEST is mounted with another fstype, unmount it then bindfs
#   --help, -h               Show help
#
# Notifications:
#   - If /opt/scripts_local/shared_functions.sh defines notify(), it is used.
#   - Else if /etc/apprise.env defines APPRISE_URL and apprise is installed, it is used.
# Log: /var/log/mount_network.log
# -----------------------------------------------------------------------------

set -Eeuo pipefail

SCRIPT_PATH="$(readlink -f "$0")"
SCRIPT_NAME="$(basename "$SCRIPT_PATH")"
SCRIPT_BASE="${SCRIPT_PATH%.*}"      # enlève l'extension (.sh)
CONFIG_FILE="${SCRIPT_BASE}.conf"    # remplace .sh par .conf
LOG_FILE="/var/log/mount_network.log"

DRY_RUN=0
VERBOSE=0
CREATE_SRC=1
FORCE_UNMOUNT_MODE="none"   # none|defined|all
REPLACE_CONFLICTS=0         # --replace-conflicts

# ----- logging to file + console ---------------------------------------------
mkdir -p "$(dirname "$LOG_FILE")"
exec > >(tee -a "$LOG_FILE") 2>&1

# ----- single-run lock (use /tmp to avoid perms issues) -----------------------
LOCK_FILE="/tmp/bind_mount.lock"
: > "$LOCK_FILE" 2>/dev/null || true
if ! exec {LOCKFD}>"$LOCK_FILE" 2>/dev/null; then
  echo "❌ Impossible d'ouvrir le fichier de verrou: $LOCK_FILE"
  exit 1
fi
if ! flock -n "$LOCKFD"; then
  echo "⚠️  $SCRIPT_NAME déjà en cours (lock: $LOCK_FILE). Abandon."
  exit 0
fi

# Cleanup: always release lock + delete file
cleanup() {
  # close FD (releases flock), then remove file
  if [[ -n "${LOCKFD:-}" ]]; then
    flock -u "$LOCKFD" || true
    exec {LOCKFD}>&- || true
  fi
  rm -f "$LOCK_FILE" 2>/dev/null || true
}
trap cleanup EXIT

# ----- helpers ----------------------------------------------------------------
print_help() {
  cat <<EOF
Usage: $SCRIPT_NAME [options]
  --config=PATH            (default: $CONFIG_FILE)
  --dry-run                Affiche les actions sans exécuter
  --verbose, -v            Mode verbeux
  --no-create-src          N’initie pas la création du dossier source manquant
  --force-unmount=defined  Démonte uniquement les bindfs définis dans la conf
  --force-unmount=all      Démonte tous les bindfs (fuse.bindfs) du système
  --replace-conflicts      Démonte un montage non-bindfs existant sur DEST avant bindfs
  --help, -h               Cette aide

Format (7 colonnes):
  src | dest | presented_uid | presented_gid | view_perms | applied_user | create_file_perms
EOF
}
ts() { date '+%F %T'; }
log() { echo "[$(ts) - $(hostname)] $*"; }
dbg() { [[ $VERBOSE -eq 1 ]] && echo "🔎 $*"; }
require_root() { (( EUID == 0 )) || { echo "❌ Exécuter en root (sudo)."; exit 1; }; }

# Notifications: shared_functions.sh > apprise > no-op
[[ -f /opt/scripts_local/shared_functions.sh ]] && source /opt/scripts_local/shared_functions.sh || true
_notify_impl() {
  local msg="$1"
  if declare -F notify >/dev/null 2>&1; then
    notify "$msg"
  elif [[ -f /etc/apprise.env ]]; then
    source /etc/apprise.env
    if [[ -n "${APPRISE_URL:-}" ]] && command -v apprise >/dev/null 2>&1; then
      apprise -b "[$(hostname)] [$SCRIPT_NAME] $msg" "$APPRISE_URL" || true
    fi
  fi
}
notify_ok()  { _notify_impl "✅ $*"; }
notify_err() { _notify_impl "❌ $*"; }

on_err() { notify_err "Échec à la ligne $1 — commande: $2"; exit 1; }
trap 'on_err "$LINENO" "$BASH_COMMAND"' ERR

trim() { xargs <<<"$1"; }

retry() {
  local tries=$1; shift
  local delay=$1; shift
  local label="$1"; shift
  local n=1
  while (( n <= tries )); do
    dbg "Tentative $n/$tries: $label"
    if (( DRY_RUN )); then
      echo "[DRY-RUN] $*"
      return 0
    fi
    if "$@"; then
      return 0
    fi
    log "❌ $label (tentative $n) échouée"
    sleep "$delay"
    ((n++))
  done
  return 1
}

ensure_pkg() {
  local pkg="$1"
  if ! dpkg -s "$pkg" >/dev/null 2>&1; then
    log "📦 Installation de $pkg…"
    apt-get update -y
    apt-get install -y "$pkg"
  else
    dbg "✓ $pkg déjà installé"
  fi
}

is_bindfs_mount() {
  local target="$1"
  [[ "$(findmnt -no FSTYPE --target "$target" 2>/dev/null || true)" == "fuse.bindfs" ]]
}

mounted_from_equals() {
  local target="$1" src="$2"
  [[ "$(findmnt -no SOURCE --target "$target" 2>/dev/null || true)" == "$src" ]]
}

# ----- args -------------------------------------------------------------------
for arg in "$@"; do
  case "$arg" in
    --config=*)              CONFIG_FILE="${arg#*=}";;
    --dry-run)               DRY_RUN=1;;
    --verbose|-v)            VERBOSE=1;;
    --no-create-src)         CREATE_SRC=0;;
    --force-unmount=defined) FORCE_UNMOUNT_MODE="defined";;
    --force-unmount=all)     FORCE_UNMOUNT_MODE="all";;
    --replace-conflicts)     REPLACE_CONFLICTS=1;;
    --help|-h)               print_help; exit 0;;
    *) echo "Option inconnue: $arg"; print_help; exit 1;;
  esac
done

require_root
ensure_pkg bindfs || true
command -v apprise >/dev/null 2>&1 || true

# ----- config presence ---------------------------------------------------------
[[ -f "$CONFIG_FILE" ]] || { echo "❌ Fichier de config introuvable: $CONFIG_FILE"; exit 1; }

# ----- unmount flows -----------------------------------------------------------
if [[ "$FORCE_UNMOUNT_MODE" == "defined" ]]; then
  log "Démontage des bindfs définis dans la conf…"
  while IFS= read -r raw; do
    [[ -z "$raw" || "$raw" =~ ^[[:space:]]*# ]] && continue
    IFS='|' read -r SRC DEST PRESENT_UID PRESENT_GID VIEW_PERMS APPLIED_USER CREATE_FILE_PERMS <<<"$raw"
    DEST="$(trim "${DEST:-}")"
    [[ -z "$DEST" ]] && { dbg "DEST vide: $raw"; continue; }
    if is_bindfs_mount "$DEST"; then
      log "🔻 umount $DEST"
      (( DRY_RUN )) || umount -l "$DEST" || log "⚠️  Impossible de démonter $DEST"
    else
      dbg "Non monté (bindfs): $DEST"
    fi
  done < "$CONFIG_FILE"
  log "Démontage (defined) terminé."
  exit 0
elif [[ "$FORCE_UNMOUNT_MODE" == "all" ]]; then
  log "Démontage de tous les bindfs…"
  for mnt in $(awk '$3 ~ /fuse.bindfs/ {print $2}' /proc/mounts); do
    log "🔻 umount $mnt"
    (( DRY_RUN )) || umount -l "$mnt" || log "⚠️  Impossible de démonter $mnt"
  done
  log "Démontage (all) terminé."
  exit 0
fi

# ----- main bindfs mount -------------------------------------------------------
mount_bindfs() {
  local SRC="$1" DST="$2" PRESENT_UID="$3" PRESENT_GID="$4" VIEW_PERMS="$5" APPLIED_USER="$6" CREATE_FILE_PERMS="$7"

  SRC="$(trim "${SRC:-}")"; DST="$(trim "${DST:-}")"
  PRESENT_UID="$(trim "${PRESENT_UID:-}")"; PRESENT_GID="$(trim "${PRESENT_GID:-}")"
  VIEW_PERMS="$(trim "${VIEW_PERMS:-}")"; APPLIED_USER="$(trim "${APPLIED_USER:-}")"
  CREATE_FILE_PERMS="$(trim "${CREATE_FILE_PERMS:-}")"

  [[ -z "$SRC" || -z "$DST" ]] && { dbg "SRC/DST manquant(s) — saut"; return 0; }

  if [[ ! -d "$SRC" ]]; then
    if (( CREATE_SRC )); then
      log "📁 Création source: $SRC"
      (( DRY_RUN )) || mkdir -p "$SRC"
      if [[ -n "$APPLIED_USER" && "$APPLIED_USER" != "-" ]] && id -u "$APPLIED_USER" >/dev/null 2>&1; then
        dbg "chown $APPLIED_USER:$APPLIED_USER $SRC"
        (( DRY_RUN )) || chown "$APPLIED_USER:$APPLIED_USER" "$SRC"
      fi
    else
      dbg "Source inexistante et --no-create-src: $SRC"
      return 0
    fi
  fi

  if [[ ! -d "$DST" ]]; then
    log "📁 Création dest: $DST"
    (( DRY_RUN )) || mkdir -p "$DST"
  fi

  # Idempotence silencieuse : déjà bindfs + même source => skip sans bruit
  if is_bindfs_mount "$DST" && mounted_from_equals "$DST" "$SRC"; then
    dbg "✓ Déjà monté (bindfs) $SRC → $DST (skip)"
    return 0
  fi

  # Déjà monté avec un autre type => skip silencieux (log seulement en --verbose)
  if findmnt -no TARGET "$DST" >/dev/null 2>&1 && ! is_bindfs_mount "$DST"; then
    if (( REPLACE_CONFLICTS )); then
      log "♻️  $DST monté avec autre type → unmount (replace-conflicts)"
      if (( DRY_RUN )); then
        echo "[DRY-RUN] umount -l \"$DST\""
      else
        umount "$DST" 2>/dev/null || umount -l "$DST" || {
          log "❌ Impossible de démonter $DST (conflit)"; return 1;
        }
      fi
    else
      dbg "⏭️  $DST déjà monté (type≠bindfs) — skip (utilise --replace-conflicts pour forcer)"
      return 0
    fi
  fi

  [[ -z "$PRESENT_UID" || "$PRESENT_UID" == "-" ]] && PRESENT_UID="root"
  [[ -z "$PRESENT_GID" || "$PRESENT_GID" == "-" ]] && PRESENT_GID="root"

  local opts=( "--force-user=$PRESENT_UID" "--force-group=$PRESENT_GID" "--chown-ignore" "--chgrp-ignore" )
  [[ -n "$VIEW_PERMS" && "$VIEW_PERMS" != "-" ]] && opts+=( "--perms=$VIEW_PERMS" )
  if [[ -n "$APPLIED_USER" && "$APPLIED_USER" != "-" ]]; then
    opts+=( "--create-for-user=$APPLIED_USER" "--create-for-group=$APPLIED_USER" )
  fi
  if [[ -n "$CREATE_FILE_PERMS" && "$CREATE_FILE_PERMS" =~ ^[0-7]{3,4}$ ]]; then
    opts+=( "--create-with-perms=$CREATE_FILE_PERMS" )
  fi

  log "🔗 bindfs ${opts[*]} \"$SRC\" \"$DST\""
  retry 3 3 "bindfs $SRC → $DST" bindfs "${opts[@]}" "$SRC" "$DST" \
    || { log "❌ Échec bindfs $SRC → $DST"; return 1; }

  dbg "✓ FSTYPE: $(findmnt -no FSTYPE --target "$DST" 2>/dev/null || true)"
  return 0
}

# ----- run ---------------------------------------------------------------------
log "=== Montages bindfs depuis $CONFIG_FILE ==="
ok_count=0; fail_count=0; skip_count=0; line_no=0

while IFS= read -r line || [[ -n "$line" ]]; do
  ((++line_no))
  [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue

  IFS='|' read -r SRC DEST PRESENT_UID PRESENT_GID VIEW_PERMS APPLIED_USER CREATE_FILE_PERMS <<<"$line"

  # Montre seulement en verbose si la ligne est incomplète
  if [[ -z "${DEST:-}" || -z "${SRC:-}" ]]; then
    dbg "Ligne $line_no invalide (src/dest manquant): $line"
    ((++skip_count))
    continue
  fi

  # Tente le montage; si déjà monté ou autre type => la fonction return 0 (skip silencieux)
  if mount_bindfs "$SRC" "$DEST" "$PRESENT_UID" "$PRESENT_GID" "$VIEW_PERMS" "$APPLIED_USER" "$CREATE_FILE_PERMS"; then
    # Pour distinguer un "vrai" montage d'un skip silencieux, on peut vérifier si c'était déjà bindfs:
    if is_bindfs_mount "$DEST" && mounted_from_equals "$DEST" "$SRC"; then
      # déjà monté avant l'appel ou monté par cet appel, on ne sait pas — on compte comme OK si l'appel a réussi ET qu'avant ce n’était pas bindfs
      # Afin de respecter "aucune notif si déjà monté", on compte en OK seulement si on vient réellement de monter.
      # Heuristique: si l'appel a réussi et qu'avant ce n'était PAS bindfs, c'était un vrai montage -> OK++.
      : # on ne peut pas différencier proprement sans snapshot d'état; on choisit de ne PAS incrémenter ici
      ((++skip_count))
    else
      ((++ok_count))
    fi
  else
    ((++fail_count))
  fi
done < "$CONFIG_FILE"

log "=== Terminé: OK=$ok_count  SKIP=$skip_count  FAIL=$fail_count ==="

# Notifications :
# - S'il y a des erreurs -> notify_err
# - S'il y a eu de vrais montages -> notify_ok
# - Si tout était déjà monté (OK=0 FAIL=0), aucune notification
if (( fail_count > 0 )); then
  notify_err "Montages bindfs avec erreurs (FAIL=$fail_count)."
elif (( ok_count > 0 )); then
  notify_ok "Montages bindfs effectués (OK=$ok_count)."
fi

exit $(( fail_count > 0 ))