#!/usr/bin/env bash
#
# Déploiement de SensiTools sur le serveur OVH.
#
# À lancer sur le serveur (SSH), après que Codex/Claude ont poussé le code :
#   cd ~/apps/SensiTools-Gestion-de-Projet/sos-symfony && bash scripts/deploy.sh
# (NE PAS confondre avec ~/www, qui est conservé — l'app vit dans ~/apps/.)
#
# Enchaîne : sauvegarde de sécurité -> récupération du code -> dépendances ->
# migrations -> vidage du cache. S'arrête à la première erreur (set -e) : en
# cas de souci, la sauvegarde prise juste avant permet de restaurer.
#
# Environnement OVH cible constaté :
#   - PHP 8.1.34
#   - Git, gzip et rsync disponibles
#   - Composer non installé globalement
#
# Composer est donc résolu localement au projet :
#   - COMPOSER_BIN=/chemin/personnalisé si défini
#   - sinon ./composer.phar
# Le script ne suppose jamais que la commande globale `composer` existe.
#
# Validation restauration :
#   - la sauvegarde avant déploiement reste inchangée ;
#   - pour vérifier réellement la compatibilité mysqldump/client MariaDB 10.3
#     vers MySQL 8.4, activer explicitement :
#       DEPLOY_VALIDATE_BACKUP_RESTORE=1
#       RESTORE_TEST_DATABASE_URL=mysql://...
#   - la restauration se fait uniquement sur cette base de test dédiée.

set -euo pipefail

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

export APP_ENV="${APP_ENV:-prod}"

STATUS_DIR="${PROJECT_DIR}/var/status"
write_deploy_status() { # $1=status $2=message
    mkdir -p "${STATUS_DIR}" 2>/dev/null || return 0
    printf '{"task":"deploy","status":"%s","message":"%s","at":"%s"}\n' \
        "$1" "$(printf '%s' "$2" | sed 's/\\/\\\\/g; s/"/\\"/g')" \
        "$(date '+%Y-%m-%dT%H:%M:%S%z')" > "${STATUS_DIR}/deploy.json" 2>/dev/null || true
}
trap 'write_deploy_status "error" "Échec du déploiement (voir logs)."' ERR

COMPOSER_COMMAND=()
resolve_composer() {
    if [[ -n "${COMPOSER_BIN:-}" ]]; then
        if [[ -x "${COMPOSER_BIN}" || -f "${COMPOSER_BIN}" ]]; then
            if [[ "${COMPOSER_BIN}" == *.phar ]]; then
                COMPOSER_COMMAND=(php "${COMPOSER_BIN}")
            else
                COMPOSER_COMMAND=("${COMPOSER_BIN}")
            fi
            return 0
        fi

        echo "ERREUR : COMPOSER_BIN est défini mais introuvable : ${COMPOSER_BIN}" >&2
        return 1
    fi

    if [[ -f "${PROJECT_DIR}/composer.phar" ]]; then
        COMPOSER_COMMAND=(php "${PROJECT_DIR}/composer.phar")
        return 0
    fi

    echo "ERREUR : Composer n'est pas disponible localement." >&2
    echo "Ajoutez composer.phar à la racine du projet ou définissez COMPOSER_BIN." >&2
    echo "Exemple contrôlé : php composer.phar install --no-dev --optimize-autoloader --no-interaction" >&2
    return 1
}

latest_backup_dump() {
    local backup_dir="${BACKUP_DIR:-${HOME}/sensitools-backups}"
    find "${backup_dir}/daily" -maxdepth 1 -type f \( -name '*.sql.gz' -o -name '*.sql.gz.gpg' \) -print 2>/dev/null | sort | tail -n1
}

echo "== [1/5] Sauvegarde de sécurité avant déploiement =="
bash "${SCRIPT_DIR}/backup-db.sh"

if [[ "${DEPLOY_VALIDATE_BACKUP_RESTORE:-0}" == "1" ]]; then
    echo "== [1b/5] Test réel du dump + restauration sur base dédiée =="
    if [[ -z "${RESTORE_TEST_DATABASE_URL:-}" ]]; then
        echo "ERREUR : RESTORE_TEST_DATABASE_URL est obligatoire avec DEPLOY_VALIDATE_BACKUP_RESTORE=1." >&2
        echo "Le test doit cibler une base de restauration dédiée, jamais la base de production." >&2
        exit 1
    fi

    latest_dump="$(latest_backup_dump)"
    if [[ -z "${latest_dump}" ]]; then
        echo "ERREUR : impossible de retrouver le dernier dump dans le dossier daily." >&2
        exit 1
    fi

    RESTORE_DATABASE_URL="${RESTORE_TEST_DATABASE_URL}" bash "${SCRIPT_DIR}/test-db-restore.sh" "${latest_dump}"
fi

echo "== [2/5] Récupération du code (git pull) =="
git pull --ff-only

echo "== [3/6] Dépendances (composer install, prod) =="
# Composer est optionnel : s'il est indisponible ET que composer.lock n'a pas changé,
# on ignore l'étape (vendor/ est déjà en place) au lieu d'AVORTER tout le déploiement
# (sinon migrations + cache ne tournent jamais). Si composer.lock a changé, on exige
# Composer (nouvelle dépendance à installer).
if git diff --name-only 'HEAD@{1}' HEAD 2>/dev/null | grep -q 'composer\.lock'; then
    composer_lock_changed=1
else
    composer_lock_changed=0
fi
if resolve_composer 2>/dev/null; then
    # Sur OVH mutualisé, le cache:clear déclenché par les auto-scripts Composer
    # peut échouer pendant le renommage atomique du cache. On garde Composer
    # strictement sur les dépendances, puis on gère le cache nous-mêmes plus bas.
    "${COMPOSER_COMMAND[@]}" install --no-dev --optimize-autoloader --no-interaction --no-scripts
elif [ "${composer_lock_changed}" = "1" ]; then
    echo "ERREUR : composer.lock a changé mais Composer est indisponible (ni composer.phar, ni COMPOSER_BIN)." >&2
    echo "Ajoutez composer.phar à la racine du projet (ou définissez COMPOSER_BIN), puis relancez." >&2
    exit 1
else
    echo "⚠ Composer indisponible et composer.lock inchangé -> étape ignorée (vendor/ conservé)."
fi

echo "== [4/6] Migrations base de données =="
php bin/console doctrine:migrations:migrate --no-interaction

echo "== [5/6] Vidage du cache =="
# Sur le FS mutualisé OVH, le renommage atomique de cache:clear échoue quand var/cache/prod
# (ou un temporaire prod_<hash> d'un clear avorté) subsiste : « Cannot rename because the
# target var/cache/prod already exists ». On nettoie AVANT pour supprimer le conflit à la
# racine, plutôt que d'y réagir après coup.
rm -rf var/cache/prod var/cache/prod_* var/cache/prod~ var/cache/prod_old
if ! php bin/console cache:clear --no-warmup; then
    echo "   ⚠ cache:clear a encore échoué ; nettoyage forcé de var/cache/prod*."
    rm -rf var/cache/prod var/cache/prod_*
fi
php bin/console cache:warmup

echo "== [6/6] Réinitialisation de l'OPcache web =="
# cache:clear ci-dessus ne vide que l'OPcache du CLI. Le process WEB a un OPcache
# SÉPARÉ qui garde le bytecode compilé (ancien conteneur) -> 500 « class not found »
# après un déplacement/renommage de classes, même si le CLI est sain.
# On force sa purge via un mini-script public qui s'auto-supprime au premier accès.
# Appel de préférence en localhost ; repli sur une URL à ouvrir depuis un navigateur
# (l'egress externe est bloqué sur OVH, donc curl serveur -> Internet échoue).
DEPLOY_PUBLIC_URL="${DEPLOY_PUBLIC_URL:-https://projets.sensipode.com}"
DEPLOY_PUBLIC_HOST="$(printf '%s' "${DEPLOY_PUBLIC_URL}" | sed -E 's#^https?://##; s#/.*$##')"
rm -f public/_opreset_*.php 2>/dev/null || true  # nettoie d'éventuels reliquats
OPRESET_FILE="_opreset_$(date +%s)_${RANDOM}.php"
cat > "public/${OPRESET_FILE}" <<'PHP'
<?php
if (function_exists('opcache_reset')) { opcache_reset(); }
@unlink(__FILE__);
header('Content-Type: text/plain; charset=utf-8');
echo "OPcache web reinitialise (fichier auto-supprime).";
PHP
if curl -fsS --max-time 5 -H "Host: ${DEPLOY_PUBLIC_HOST}" "http://127.0.0.1/${OPRESET_FILE}" >/dev/null 2>&1; then
    echo "   OPcache web réinitialisé automatiquement (localhost)."
else
    echo "   ⚠ Reset auto impossible (egress OVH). Ouvre UNE fois cette URL dans TON navigateur :"
    echo "        ${DEPLOY_PUBLIC_URL}/${OPRESET_FILE}"
    echo "     (le fichier s'auto-supprime au premier accès ; avec le .user.ini en place,"
    echo "      le web se met de toute façon à jour seul en quelques secondes)"
fi

write_deploy_status "ok" "Déploiement réussi."
echo "== Déploiement terminé avec succès =="
