#!/usr/bin/env bash
# Paso 1 del upgrade PHP 8.3 en feadulta.com (issue #46):
# subir 5 ficheros parcheados + limpieza malware, manteniendo PHP 7.4.
#
# Modos:
#   --dry-run   imprime lo que haría sin tocar nada (default)
#   --apply     ejecuta el despliegue real
#   --rollback BACKUP_DIR  restaura desde un backup pre-step1 concreto
#
# Pre-flight estricto: aborta si los 5 ficheros remotos no coinciden con el
# hash esperado (el que verificamos hoy 2026-05-25). Si en prod cambió algo
# entre planning y ejecución, hay que rehacer la planificación.

set -euo pipefail

# ─── Config ────────────────────────────────────────────────────────────────
SSH_USER="feadulta"
SSH_HOST="134.0.10.170"
SSH_PASS='6Rm2qOF@eundwpda'

REMOTE_WEB_ROOT="/web"
LOCAL_SRC_ROOT="/home/rafa/joomla-migration/joomla-php83"
BACKUP_ROOT="/home/rafa/joomla-migration/backup/prod-20260525-php83-compat"

# Mapa: ruta_relativa | hash_local_esperado | hash_remoto_esperado_actual
# (verificado 2026-05-25 con md5sum local + ssh + tar -xzO del backup)
declare -a FILES=(
  "modules/mod_featcats/helper.php|01ae5ad40e13abdcd5852897786d3733|744922888ae533b090eb34effe3bb469"
  "templates/fe_adulta_1/functions.php|06b2c26a618dbceefa5d8d5f0293c2cf|6318edec84cdf7a6b84a1d07f063c6c0"
  "templates/fe_adulta_1/index.php|dc318909b9ae5976e5c4f01c06c35f66|9793dfa3c880eba37ad5ad35e6988705"
  "modules/mod_k2_filter/helper.php|0d9767a0d8d67aa420baefe38382a87c|8530a4e70043973fd2d625c6f8de6ce9"
  "modules/mod_k2_filter/tmpl/Default/template.php|a62a39dacafa0748528268b9f04aa844|008465147acdeb442eca6f64311fb23d"
)

# URLs de smoke test (deben devolver HTTP 200 antes y después del cambio)
declare -a SMOKE_URLS=(
  "https://www.feadulta.com/"
  "https://www.feadulta.com/es/"
  "https://www.feadulta.com/es/ayuda.html"
  "https://www.feadulta.com/es/quienessomos/colaboradores.html"
  "https://www.feadulta.com/es/buscadoravanzado/itemlist/"
  "https://www.feadulta.com/es/buscadoravanzado/itemlist/user/43-fraymarcos.html"
  "https://www.feadulta.com/es/comentcol2.html"
)

# Patrones de spam que deben dar 0 hits en las respuestas tras el paso 1
SPAM_REGEX="(apuestadeportiva|vavada\.mobi|inkabet|betsafe|betcris|botbotbot)"

# IP del origen para bypass de Cloudflare (la web está detrás de CF managed challenge,
# las peticiones curl normales reciben 403). --resolve nos lleva directo al origen.
ORIGIN_IP="134.0.10.170"
SMOKE_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"

# ─── Utilidades ────────────────────────────────────────────────────────────
# Logs van a stderr; stdout queda libre para "datos" (p.ej. ruta del backup
# para capturar con $(...) sin contaminación). Bug detectado en revisión #46.
log()  { printf '%s [%s] %s\n' "$(date +'%H:%M:%S')" "$1" "$2" >&2; }
info() { log "INFO" "$1"; }
warn() { log "WARN" "$1"; }
err()  { log "ERR " "$1"; }
ok()   { log "OK  " "$1"; }

ssh_run() {
  SSHPASS="$SSH_PASS" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=15 \
    "$SSH_USER@$SSH_HOST" "$@"
}

# El cPanel jail rechaza scp y sftp ("Connection closed"). Usamos cat-over-ssh,
# que sí funciona (cat está en /usr/bin/cat). Verificación 2026-05-25: download
# de un fichero conocido reproduce el mismo MD5; upload de bytes y verificación
# por re-lectura sin pérdidas.
scp_get() {
  local remote="$1" local_target="$2"
  SSHPASS="$SSH_PASS" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=15 \
    "$SSH_USER@$SSH_HOST" "cat '$remote'" > "$local_target"
}

scp_put() {
  local local_src="$1" remote_target="$2"
  SSHPASS="$SSH_PASS" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=15 \
    "$SSH_USER@$SSH_HOST" "cat > '$remote_target'" < "$local_src"
}

remote_md5() {
  ssh_run "md5sum '$1' 2>/dev/null | cut -d' ' -f1"
}

# ─── Pre-flight checks ─────────────────────────────────────────────────────
preflight() {
  info "Pre-flight: verificar hashes locales y remotos"
  local fail=0

  for entry in "${FILES[@]}"; do
    IFS='|' read -r rel hl hr_expected <<<"$entry"
    local local_path="$LOCAL_SRC_ROOT/$rel"
    local remote_path="$REMOTE_WEB_ROOT/$rel"

    if [[ ! -f "$local_path" ]]; then
      err "Local no existe: $local_path"; fail=1; continue
    fi
    local hl_actual
    hl_actual=$(md5sum "$local_path" | cut -d' ' -f1)
    if [[ "$hl_actual" != "$hl" ]]; then
      err "Local $rel: hash $hl_actual ≠ esperado $hl"; fail=1
    else
      ok "Local $rel coincide ($hl)"
    fi

    local hr_actual
    hr_actual=$(remote_md5 "$remote_path")
    if [[ -z "$hr_actual" ]]; then
      err "Remoto no existe o vacío: $remote_path"; fail=1
    elif [[ "$hr_actual" != "$hr_expected" ]]; then
      err "Remoto $rel: hash $hr_actual ≠ esperado $hr_expected (algo cambió en prod, replanificar)"; fail=1
    else
      ok "Remoto $rel coincide ($hr_actual)"
    fi
  done

  if [[ $fail -eq 1 ]]; then
    err "Pre-flight FAIL → abortando"
    exit 2
  fi
  ok "Pre-flight OK"
}

# ─── Backup pre-cambio (los 5 ficheros remotos) ────────────────────────────
backup_pre_step1() {
  local ts; ts=$(date +'%Y%m%d-%H%M%S')
  local dir="$BACKUP_ROOT/pre-step1-$ts"

  if [[ "$MODE" == "dry-run" ]]; then
    info "[dry-run] Crearía directorio: $dir"
    info "[dry-run] Descargaría ${#FILES[@]} ficheros remotos a ese directorio + md5sums.txt + tar.gz"
    echo "$dir" # imprime para usar después
    return
  fi

  info "Creando backup pre-step1 en $dir"
  mkdir -p "$dir"

  for entry in "${FILES[@]}"; do
    IFS='|' read -r rel _ hr_expected <<<"$entry"
    local remote_path="$REMOTE_WEB_ROOT/$rel"
    local local_target="$dir/$rel"
    mkdir -p "$(dirname "$local_target")"
    info "  Descargando $remote_path"
    scp_get "$remote_path" "$local_target"
    if [[ ! -s "$local_target" ]]; then
      err "Descarga vacía: $local_target — ABORT (revisar acceso SSH)"
      exit 6
    fi
    local h_after
    h_after=$(md5sum "$local_target" | cut -d' ' -f1)
    if [[ "$h_after" != "$hr_expected" ]]; then
      err "Backup $rel: md5 $h_after ≠ esperado $hr_expected — ABORT"
      exit 6
    fi
    ok "  Backup $rel verificado ($h_after)"
  done

  (cd "$dir" && find . -type f -name '*.php' -exec md5sum {} \; > md5sums.txt)
  tar -czf "$dir.tar.gz" -C "$BACKUP_ROOT" "pre-step1-$ts"
  ok "Backup pre-step1 creado: $dir"
  ok "Backup tar.gz: $dir.tar.gz"
  echo "$dir"
}

# ─── Subida de los 5 ficheros ──────────────────────────────────────────────
upload_files() {
  for entry in "${FILES[@]}"; do
    IFS='|' read -r rel hl _ <<<"$entry"
    local local_path="$LOCAL_SRC_ROOT/$rel"
    local remote_path="$REMOTE_WEB_ROOT/$rel"

    if [[ "$MODE" == "dry-run" ]]; then
      info "[dry-run] scp $local_path → $remote_path"
      continue
    fi

    info "Subiendo $rel"
    scp_put "$local_path" "$remote_path"
    local hr_after; hr_after=$(remote_md5 "$remote_path")
    if [[ "$hr_after" != "$hl" ]]; then
      err "Subida de $rel: hash remoto post-subida $hr_after ≠ local $hl"
      err "ABORT — el fichero remoto no coincide con el local. Considerar rollback."
      exit 3
    fi
    ok "Subido + verificado $rel ($hr_after)"
  done
}

# ─── Smoke test HTTP ───────────────────────────────────────────────────────
# allow_spam=1 → solo validar HTTP code (uso post-rollback, donde los ficheros
# restaurados llevan las inyecciones spam originales y un spam>0 es esperado).
smoke_test() {
  local allow_spam="${1:-0}"
  if [[ "$allow_spam" -eq 1 ]]; then
    info "Smoke test HTTP (post-rollback: solo validar HTTP, spam>0 esperado)"
  else
    info "Smoke test HTTP (HTTP 200/3xx + spam=0)"
  fi
  local fail=0
  for url in "${SMOKE_URLS[@]}"; do
    if [[ "$MODE" == "dry-run" ]]; then
      info "[dry-run] curl $url + grep spam"
      continue
    fi
    local code body_spam tmp
    tmp=$(mktemp)
    code=$(curl -ksL -A "$SMOKE_UA" --resolve "www.feadulta.com:443:$ORIGIN_IP" \
             -o "$tmp" -w "%{http_code}" "$url" || echo "ERR")
    body_spam=$(grep -cE "$SPAM_REGEX" "$tmp" || true)
    rm -f "$tmp"
    if [[ "$code" != "200" && "$code" != "301" && "$code" != "302" ]]; then
      err "  $url → HTTP $code"; fail=1
    elif [[ "$allow_spam" -eq 0 && "$body_spam" -gt 0 ]]; then
      err "  $url → HTTP $code, $body_spam strings spam"; fail=1
    else
      ok "  $url → HTTP $code, $body_spam spam"
    fi
  done
  if [[ $fail -eq 1 ]]; then
    err "Smoke test FAIL → considerar rollback manual con --rollback <backup-dir>"
    exit 4
  fi
  ok "Smoke test OK"
}

# ─── Rollback desde backup ─────────────────────────────────────────────────
rollback() {
  local dir="$1"
  if [[ ! -d "$dir" ]]; then
    err "Backup dir no existe: $dir"; exit 5
  fi
  info "Rollback desde $dir"
  for entry in "${FILES[@]}"; do
    IFS='|' read -r rel _ _ <<<"$entry"
    local local_src="$dir/$rel"
    local remote_path="$REMOTE_WEB_ROOT/$rel"
    if [[ ! -f "$local_src" ]]; then
      err "  No hay backup para $rel — saltando"; continue
    fi
    info "  Restaurando $rel"
    scp_put "$local_src" "$remote_path"
    ok "  Restaurado $rel"
  done
  ok "Rollback completado"
}

# ─── Main ──────────────────────────────────────────────────────────────────
MODE="dry-run"
ROLLBACK_DIR=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run) MODE="dry-run"; shift ;;
    --apply) MODE="apply"; shift ;;
    --rollback) MODE="rollback"; ROLLBACK_DIR="${2:-}"; shift 2 ;;
    -h|--help) sed -n '1,12p' "$0"; exit 0 ;;
    *) err "Flag desconocido: $1"; exit 1 ;;
  esac
done

info "Modo: $MODE"

if [[ "$MODE" == "rollback" ]]; then
  if [[ -z "$ROLLBACK_DIR" ]]; then err "--rollback requiere ruta del backup"; exit 1; fi
  rollback "$ROLLBACK_DIR"
  smoke_test 1   # allow_spam=1: ficheros restaurados aún tienen las inyecciones
  exit 0
fi

preflight
backup_dir=$(backup_pre_step1)
upload_files
smoke_test

ok "Paso 1 finalizado en modo: $MODE"
if [[ "$MODE" == "apply" ]]; then
  ok "Backup pre-step1: $backup_dir"
  ok "Para rollback: $0 --rollback $backup_dir"
fi
