#!/bin/bash
#
# NimiApps · Corrección ENT-003-001 sobre la versión 0.3.0 (ENT-003)
# Formulario «Nueva promoción»: no se veía el campo del valor del descuento.
#
# Uso:
#   bash corregir.sh RUTA_DE_LA_INSTALACION --simular   (solo comprueba, no cambia nada)
#   bash corregir.sh RUTA_DE_LA_INSTALACION             (aplica la corrección)
#
# Qué hace:
#   1. Comprueba la integridad del paquete (manifiesto/paquete.sha256).
#   2. Comprueba que la ruta es una instalación de NimiApps 0.3.0 con .env.
#   3. Exige que la aplicación esté en modo mantenimiento (php artisan down).
#   4. Comprueba que los archivos que se van a sustituir no tienen cambios locales
#      (deben ser los de la 0.3.0 o ya los corregidos).
#   5. Guarda una copia de los archivos que sustituye FUERA de public_html.
#   6. Copia los archivos (una vista, un controlador, dos pruebas, CHECKSUMS.sha256
#      y el documento de la corrección).
#   7. Verifica las huellas SHA-256 de lo copiado.
#
# Qué NO hace: no cambia la versión (sigue siendo 0.3.0), no toca .env, la base
# de datos, storage/, vendor/, bootstrap/cache/, .htaccess ni public/.htaccess,
# y no necesita migraciones.
#
# Se detiene ante el primer error. Compatible con bash 4.2 (CentOS 7).

set -Eeuo pipefail
umask 022

VERSION_REQUERIDA="0.3.0"
CORRECCION="ENT-003-001"

PAQUETE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CARGA="$PAQUETE/archivos"
MANIFIESTO="$PAQUETE/manifiesto"

# ---------------------------------------------------------------- utilidades

rojo()  { printf '\033[31m%s\033[0m\n' "$*"; }
verde() { printf '\033[32m%s\033[0m\n' "$*"; }
info()  { printf '  %s\n' "$*"; }
paso()  { printf '\n\033[1m== %s\033[0m\n' "$*"; }
error() { rojo "ERROR: $*"; rojo "La corrección se ha detenido."; exit 1; }

uso() {
    echo "Uso: bash corregir.sh RUTA_DE_LA_INSTALACION [--simular]"
    echo "Ejemplo: bash corregir.sh /home/usuario/public_html/staging.nimiapps.com --simular"
    exit 2
}

# Versión declarada en config/microapps.php ('version' => 'x.y.z').
leer_version() {
    sed -n "s/^[[:space:]]*'version'[[:space:]]*=>[[:space:]]*'\([0-9][0-9.]*\)'.*/\1/p" "$1" | head -n 1
}

huella() { sha256sum "$1" | awk '{print $1}'; }

# Huella esperada de un archivo en un listado "hash  ruta".
huella_en() {
    awk -v f="$2" '{ h=$1; $1=""; sub(/^ +/, ""); if ($0 == f) { print h; exit } }' "$1"
}

# ---------------------------------------------------------------- argumentos

DESTINO=""
SIMULAR=0
for arg in "$@"; do
    case "$arg" in
        --simular) SIMULAR=1 ;;
        -h|--help|--ayuda) uso ;;
        -*) echo "Opción desconocida: $arg"; uso ;;
        *) [ -z "$DESTINO" ] && DESTINO="$arg" || uso ;;
    esac
done
[ -n "$DESTINO" ] || uso

echo "NimiApps $VERSION_REQUERIDA · corrección $CORRECCION (formulario de promociones)"
[ "$SIMULAR" -eq 1 ] && echo "MODO SIMULACIÓN: no se modificará ningún archivo."

# ---------------------------------------------------------------- 1. entorno

paso "1. Entorno"
[ "$(id -u)" -ne 0 ] || error "no ejecutes este script como root; usa el usuario de la cuenta de cPanel."
for herramienta in sha256sum awk sed cp mv mkdir date; do
    command -v "$herramienta" >/dev/null 2>&1 || error "falta la herramienta '$herramienta'."
done

PHP_BIN="${PHP:-php}"
command -v "$PHP_BIN" >/dev/null 2>&1 || error "no se encuentra PHP ('$PHP_BIN'). Indica la ruta: PHP=/opt/cpanel/ea-php83/root/usr/bin/php bash corregir.sh ..."
PHP_VERSION="$("$PHP_BIN" -r 'echo PHP_VERSION;')"
"$PHP_BIN" -r 'exit(version_compare(PHP_VERSION, "8.3.0", ">=") ? 0 : 1);' \
    || error "PHP $PHP_VERSION es demasiado antiguo (se necesita 8.3 o superior). Usa PHP=/opt/cpanel/ea-php83/root/usr/bin/php"
info "Usuario: $(id -un) · PHP $PHP_VERSION ($PHP_BIN)"

# ---------------------------------------------------------------- 2. paquete

paso "2. Integridad del paquete"
for f in paquete.sha256 archivos-nuevos.txt archivos-modificados.txt v030-esperado.sha256; do
    [ -f "$MANIFIESTO/$f" ] || error "falta manifiesto/$f. Descomprime de nuevo el ZIP."
done
[ -d "$CARGA" ] || error "falta la carpeta archivos/. Descomprime de nuevo el ZIP."
( cd "$CARGA" && sha256sum -c --quiet "$MANIFIESTO/paquete.sha256" ) \
    || error "el contenido del paquete no coincide con manifiesto/paquete.sha256 (ZIP dañado o modificado)."
TOTAL_NUEVOS=$(grep -c . "$MANIFIESTO/archivos-nuevos.txt" || true)
TOTAL_MODIFICADOS=$(grep -c . "$MANIFIESTO/archivos-modificados.txt" || true)
info "Paquete correcto: $TOTAL_NUEVOS archivos nuevos y $TOTAL_MODIFICADOS modificados."

# ---------------------------------------------------------------- 3. destino

paso "3. Instalación de destino"
[ -d "$DESTINO" ] || error "no existe la carpeta '$DESTINO'."
DESTINO="$(cd "$DESTINO" && pwd -P)"
info "Ruta: $DESTINO"
case "$PAQUETE/" in
    "$DESTINO"/*) error "el paquete está descomprimido dentro de la instalación. Descomprímelo fuera (por ejemplo en tu carpeta personal)." ;;
esac
[ -f "$DESTINO/artisan" ]               || error "no hay 'artisan' en $DESTINO: no parece una instalación de NimiApps."
[ -f "$DESTINO/config/microapps.php" ]  || error "no existe config/microapps.php en $DESTINO."
[ -f "$DESTINO/vendor/autoload.php" ]   || error "no existe vendor/autoload.php en $DESTINO."
[ -f "$DESTINO/.env" ]                  || error "no existe .env en $DESTINO (este paquete no instala desde cero)."
[ -d "$DESTINO/public" ]                || error "no existe la carpeta public/ en $DESTINO."
[ -w "$DESTINO" ]                       || error "el usuario $(id -un) no puede escribir en $DESTINO."

VERSION_INSTALADA="$(leer_version "$DESTINO/config/microapps.php")"
[ -n "$VERSION_INSTALADA" ] || error "no se puede leer la versión de config/microapps.php."
info "Versión instalada: $VERSION_INSTALADA"

[ "$VERSION_INSTALADA" = "$VERSION_REQUERIDA" ] \
    || error "esta corrección es para la versión $VERSION_REQUERIDA (instalada: $VERSION_INSTALADA)."

# ¿Ya se aplicó? Se comprueba que todo coincide con el paquete.
if ( cd "$DESTINO" && cat "$MANIFIESTO/archivos-nuevos.txt" "$MANIFIESTO/archivos-modificados.txt" \
     | while IFS= read -r f; do printf '%s  %s\n' "$(huella_en "$MANIFIESTO/paquete.sha256" "$f")" "$f"; done \
     | sha256sum -c --quiet - >/dev/null 2>&1 ); then
    verde "La corrección $CORRECCION ya está aplicada: todos los archivos coinciden con este paquete."
    echo "No hay nada que copiar. Si no lo hiciste, ejecuta: $PHP_BIN artisan view:clear && $PHP_BIN artisan optimize"
    exit 0
fi

# ---------------------------------------------------------------- 4. mantenimiento

paso "4. Modo mantenimiento"
if [ -f "$DESTINO/storage/framework/down" ]; then
    info "La aplicación está en modo mantenimiento."
elif [ "$SIMULAR" -eq 1 ]; then
    info "AVISO: la aplicación NO está en modo mantenimiento. Al aplicar la corrección será obligatorio (php artisan down)."
else
    error "la aplicación no está en modo mantenimiento. Ejecuta antes: $PHP_BIN artisan down"
fi

# ---------------------------------------------------------------- 5. cambios locales

paso "5. Comprobación de cambios locales"
CONFLICTOS=0
while IFS= read -r f; do
    [ -n "$f" ] || continue
    destino_f="$DESTINO/$f"
    nueva="$(huella_en "$MANIFIESTO/paquete.sha256" "$f")"
    esperada="$(huella_en "$MANIFIESTO/v030-esperado.sha256" "$f")"
    if [ ! -f "$destino_f" ]; then
        rojo "  Falta en la instalación: $f"; CONFLICTOS=$((CONFLICTOS + 1)); continue
    fi
    actual="$(huella "$destino_f")"
    if [ "$actual" != "$esperada" ] && [ "$actual" != "$nueva" ]; then
        rojo "  Modificado localmente: $f"; CONFLICTOS=$((CONFLICTOS + 1))
    fi
done < "$MANIFIESTO/archivos-modificados.txt"

while IFS= read -r f; do
    [ -n "$f" ] || continue
    destino_f="$DESTINO/$f"
    if [ -e "$destino_f" ]; then
        nueva="$(huella_en "$MANIFIESTO/paquete.sha256" "$f")"
        if [ ! -f "$destino_f" ] || [ "$(huella "$destino_f")" != "$nueva" ]; then
            rojo "  Ya existe y es distinto: $f"; CONFLICTOS=$((CONFLICTOS + 1))
        fi
    fi
done < "$MANIFIESTO/archivos-nuevos.txt"

if [ "$CONFLICTOS" -gt 0 ]; then
    error "$CONFLICTOS archivo(s) de la instalación no coinciden con la versión 0.3.0 entregada. No se sobrescribe nada; revisa esos archivos (guárdalos aparte o restáuralos desde el ZIP de la 0.3.0) y vuelve a ejecutar."
fi
info "Sin cambios locales en los archivos afectados."
info "No se tocan: .env, .htaccess, public/.htaccess, storage/, vendor/, bootstrap/cache/, base de datos."

# ---------------------------------------------------------------- simulación

if [ "$SIMULAR" -eq 1 ]; then
    paso "Simulación terminada"
    echo "  Se copiarían $TOTAL_NUEVOS archivos nuevos y se sustituirían $TOTAL_MODIFICADOS:"
    sed 's/^/    ~ /' "$MANIFIESTO/archivos-modificados.txt"
    echo "  (lista de nuevos en manifiesto/archivos-nuevos.txt)"
    verde "Todo listo para aplicar la corrección. No se ha modificado nada."
    exit 0
fi

# ---------------------------------------------------------------- 6. copia de seguridad

paso "6. Copia de seguridad de los archivos que se sustituyen"
RAIZ_RESPALDOS="${NIMIAPPS_RESPALDOS:-$HOME/nimiapps-respaldos}"
mkdir -p "$RAIZ_RESPALDOS"
RAIZ_RESPALDOS="$(cd "$RAIZ_RESPALDOS" && pwd -P)"
case "$RAIZ_RESPALDOS/" in
    "$DESTINO"/*|*/public_html/*) error "la carpeta de copias ($RAIZ_RESPALDOS) no puede estar dentro de public_html ni de la instalación." ;;
esac
RESPALDO="$RAIZ_RESPALDOS/correccion-ent003-001-$(date +%Y%m%d-%H%M%S)"
[ ! -e "$RESPALDO" ] || error "ya existe $RESPALDO; espera un segundo y repite."
mkdir -p "$RESPALDO/archivos"
chmod 700 "$RAIZ_RESPALDOS" "$RESPALDO"
REGISTRO="$RESPALDO/registro.txt"
{
    echo "Corrección $CORRECCION sobre NimiApps $VERSION_REQUERIDA"
    echo "Fecha: $(date '+%Y-%m-%d %H:%M:%S %z')"
    echo "Instalación: $DESTINO"
    echo "Usuario: $(id -un)"
} > "$REGISTRO"

while IFS= read -r f; do
    [ -n "$f" ] || continue
    mkdir -p "$RESPALDO/archivos/$(dirname "$f")"
    cp -p "$DESTINO/$f" "$RESPALDO/archivos/$f"
done < "$MANIFIESTO/archivos-modificados.txt"
( cd "$RESPALDO/archivos" && find . -type f | sed 's|^\./||' | LC_ALL=C sort | while IFS= read -r f; do sha256sum "$f"; done ) > "$RESPALDO/respaldo.sha256"

# Archivos nuevos que no existían (los que habría que quitar para deshacer la corrección).
: > "$RESPALDO/archivos-nuevos-creados.txt"
while IFS= read -r f; do
    [ -n "$f" ] || continue
    [ -e "$DESTINO/$f" ] || echo "$f" >> "$RESPALDO/archivos-nuevos-creados.txt"
done < "$MANIFIESTO/archivos-nuevos.txt"

cat > "$RESPALDO/restaurar-archivos.sh" <<RESTAURAR
#!/bin/bash
# Devuelve los ARCHIVOS de la instalación a como estaban antes de la corrección
# ENT-003-001. La corrección no cambia la base de datos.
set -Eeuo pipefail
DESTINO="$DESTINO"
RESPALDO="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
[ -f "\$DESTINO/storage/framework/down" ] || { echo "Pon antes la aplicación en mantenimiento (php artisan down)."; exit 1; }
( cd "\$RESPALDO/archivos" && sha256sum -c --quiet "\$RESPALDO/respaldo.sha256" ) || { echo "La copia está dañada; no se restaura nada."; exit 1; }
echo "Se restaurarán los archivos de \$RESPALDO/archivos en \$DESTINO"
echo "y se borrarán los \$(grep -c . "\$RESPALDO/archivos-nuevos-creados.txt" || true) archivos que creó la corrección."
read -r -p "Escribe RESTAURAR para continuar: " respuesta
[ "\$respuesta" = "RESTAURAR" ] || { echo "Cancelado."; exit 1; }
( cd "\$RESPALDO/archivos" && find . -type f | sed 's|^\./||' ) | while IFS= read -r f; do
    mkdir -p "\$DESTINO/\$(dirname "\$f")"
    cp -p "\$RESPALDO/archivos/\$f" "\$DESTINO/\$f.restaurar.\$\$"
    mv -f "\$DESTINO/\$f.restaurar.\$\$" "\$DESTINO/\$f"
done
while IFS= read -r f; do
    [ -n "\$f" ] || continue
    rm -f "\$DESTINO/\$f"
done < "\$RESPALDO/archivos-nuevos-creados.txt"
echo "Archivos restaurados. Ejecuta: php artisan view:clear, php artisan optimize y php artisan up."
RESTAURAR
chmod 700 "$RESPALDO/restaurar-archivos.sh"
info "Copia guardada en: $RESPALDO"
echo "Copia de seguridad: $RESPALDO" >> "$REGISTRO"

# ---------------------------------------------------------------- 7. copia de archivos

paso "7. Copia de los archivos corregidos"
fallo_copia() {
    rojo "ERROR durante la copia de archivos. La instalación puede haber quedado a medias."
    rojo "La aplicación sigue en modo mantenimiento. Opciones:"
    rojo "  - Corregir el problema (espacio, permisos) y volver a ejecutar este script."
    rojo "  - Volver a los archivos anteriores con: bash $RESPALDO/restaurar-archivos.sh"
    echo "ERROR durante la copia" >> "$REGISTRO"
}
trap fallo_copia ERR

copiar() {
    local f="$1"
    local dir
    dir="$(dirname "$DESTINO/$f")"
    mkdir -p "$dir"
    cp "$CARGA/$f" "$DESTINO/$f.nimi-nuevo.$$"
    chmod 644 "$DESTINO/$f.nimi-nuevo.$$"
    mv -f "$DESTINO/$f.nimi-nuevo.$$" "$DESTINO/$f"
}

COPIADOS=0
while IFS= read -r f; do
    [ -n "$f" ] || continue
    copiar "$f"; COPIADOS=$((COPIADOS + 1))
done < <(cat "$MANIFIESTO/archivos-nuevos.txt" "$MANIFIESTO/archivos-modificados.txt")
trap - ERR
info "$COPIADOS archivos copiados."
echo "Archivos copiados: $COPIADOS" >> "$REGISTRO"

# ---------------------------------------------------------------- 8. verificación

paso "8. Verificación"
( cd "$DESTINO" && cat "$MANIFIESTO/archivos-nuevos.txt" "$MANIFIESTO/archivos-modificados.txt" \
    | while IFS= read -r f; do printf '%s  %s\n' "$(huella_en "$MANIFIESTO/paquete.sha256" "$f")" "$f"; done \
    | sha256sum -c --quiet - ) \
    || error "algún archivo copiado no coincide con el paquete. Vuelve a ejecutar el script o restaura con $RESPALDO/restaurar-archivos.sh"
info "Todos los archivos coinciden con el paquete. La versión sigue siendo $VERSION_REQUERIDA."
echo "Verificación correcta" >> "$REGISTRO"

verde ""
verde "Corrección copiada. La aplicación SIGUE en modo mantenimiento."
cat <<SIGUIENTE

Siguientes pasos (desde $DESTINO), según CORRECCION-ENT-003-001.md:
  $PHP_BIN artisan view:clear
  $PHP_BIN artisan optimize
  $PHP_BIN artisan microapps:check
  $PHP_BIN artisan up

No hay migraciones. Copia de los archivos sustituidos: $RESPALDO
SIGUIENTE
