#!/usr/bin/env bash
# =============================================================================
#  OSEP / kplan — Déploiement Docker Swarm
#  Étapes :
#   1. Charger l'environnement
#   2. Rendre le stack.yaml
#   3. Créer les volumes manquants
#   4. Déployer le stack
#   5. Forcer assets_sync + attendre la complétion (CRUCIAL pour le CSS)
#   6. Permissions runtime sur php/worker (incluant /var/www/var pour le cache)
#   7. Configurer PHP (upload limits + tmp dir)
#   8. Garantir les mounts data/tmp_uploads
#   9. Vider le cache Symfony EN TANT QUE www-data + re-fix permissions
#  10. Reporting final
# =============================================================================

set -euo pipefail

STACK_NAME=kplan
STACK_FILE_OUT=/tmp/stack.resolved.yaml
ENV_FILE=./.env.stack
ASSETS_SYNC_TIMEOUT=60   # secondes (one-shot, doit terminer < 30s normalement)
ASSETS_SYNC_FALLBACK=true # si le service ne se termine pas, on syncronise nous-mêmes
RUNTIME_USER=www-data    # utilisateur qui fait tourner php-fpm
RUNTIME_GROUP=www-data

# ----- Couleurs (désactivées hors TTY ou si NO_COLOR=1) ----
if [ -z "${NO_COLOR:-}" ] && [ -t 1 ]; then
    C_BLUE='\033[1;34m'; C_GREEN='\033[1;32m'; C_YELLOW='\033[1;33m'
    C_RED='\033[1;31m';  C_DIM='\033[2m';      C_RESET='\033[0m'
else
    C_BLUE=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_DIM=''; C_RESET=''
fi
log()  { printf "${C_BLUE}▸ %s${C_RESET}\n" "$*"; }
ok()   { printf "${C_GREEN}✓ %s${C_RESET}\n" "$*"; }
warn() { printf "${C_YELLOW}⚠ %s${C_RESET}\n" "$*"; }
err()  { printf "${C_RED}✗ %s${C_RESET}\n" "$*" >&2; }

# =============================================================================
# 1) Charger l'environnement
# =============================================================================
log "Chargement de l'environnement (${ENV_FILE})..."
[ -f "$ENV_FILE" ] || { err "Fichier $ENV_FILE introuvable"; exit 1; }
sed -i 's/\r$//' "$ENV_FILE" || true
set -a; . "$ENV_FILE"; set +a

# =============================================================================
# 2) Rendre le stack.yaml
# =============================================================================
log "Rendu du stack.yaml..."
envsubst < stack.yaml > "$STACK_FILE_OUT"

# =============================================================================
# 3) Créer les volumes manquants
# =============================================================================
log "Vérification des volumes Docker..."
for vol in tmp_uploads data; do
    if ! docker volume inspect "${STACK_NAME}_${vol}" >/dev/null 2>&1; then
        docker volume create "${STACK_NAME}_${vol}" >/dev/null
        ok "Volume ${STACK_NAME}_${vol} créé"
    fi
done

# =============================================================================
# 4) Déploiement
# =============================================================================
log "Déploiement du stack ${STACK_NAME}..."
docker stack deploy \
    -c "$STACK_FILE_OUT" \
    --with-registry-auth \
    --prune \
    "$STACK_NAME"

# =============================================================================
# 5) Forcer assets_sync + attendre la complétion
# =============================================================================
log "Forçage de la synchronisation des assets..."
docker service update --force "${STACK_NAME}_assets_sync" >/dev/null 2>&1 || true

log "Attente de la fin de assets_sync (max ${ASSETS_SYNC_TIMEOUT}s)..."
SUCCESS=false
ATTEMPTS=$((ASSETS_SYNC_TIMEOUT / 2))
for i in $(seq 1 $ATTEMPTS); do
    NEWEST_STATE=$(docker service ps "${STACK_NAME}_assets_sync" \
        --format '{{.CurrentState}}' --no-trunc 2>/dev/null | head -1 || echo "")

    case "$NEWEST_STATE" in
        Complete*|complete*)
            echo
            ok "assets_sync terminé avec succès"
            SUCCESS=true
            break
            ;;
        Failed*|failed*|Rejected*|rejected*)
            echo
            err "assets_sync a échoué : $NEWEST_STATE"
            warn "Dernières lignes de log :"
            docker service logs "${STACK_NAME}_assets_sync" --tail 30 2>&1 | sed 's/^/    /' || true
            exit 1
            ;;
        *)
            printf "."
            sleep 2
            ;;
    esac
done

if [ "$SUCCESS" = false ]; then
    echo
    warn "assets_sync n'a pas terminé dans le délai (${ASSETS_SYNC_TIMEOUT}s) — fallback manuel"
    docker service ps "${STACK_NAME}_assets_sync" --no-trunc | head -5

    if [ "$ASSETS_SYNC_FALLBACK" = true ]; then
        log "Fallback : copie manuelle du build depuis un container PHP vers kplan_public"
        SRC_CID=$(docker ps -q --filter "label=com.docker.swarm.service.name=${STACK_NAME}_php" | head -1 || true)
        if [ -n "$SRC_CID" ]; then
            VOL_NAME="${STACK_NAME}_public"
            docker volume inspect "$VOL_NAME" >/dev/null 2>&1 || VOL_NAME="kplan_public"
            VOL_PATH=$(docker volume inspect "$VOL_NAME" --format '{{.Mountpoint}}' 2>/dev/null || true)

            if [ -n "$VOL_PATH" ] && [ -d "$VOL_PATH" ]; then
                docker exec "$SRC_CID" tar -C /var/www/public -cf - build > /tmp/build.tar
                rm -rf "$VOL_PATH/build" 2>/dev/null || sudo rm -rf "$VOL_PATH/build"
                tar -C "$VOL_PATH" -xf /tmp/build.tar 2>/dev/null \
                    || sudo tar -C "$VOL_PATH" -xf /tmp/build.tar
                chown -R www-data:www-data "$VOL_PATH/build" 2>/dev/null \
                    || sudo chown -R www-data:www-data "$VOL_PATH/build"
                rm -f /tmp/build.tar

                APP_CSS=$(find "$VOL_PATH/build" -name "app.*.css" 2>/dev/null | head -1 \
                    || sudo find "$VOL_PATH/build" -name "app.*.css" | head -1)
                if [ -n "$APP_CSS" ] && grep -q "osep-" "$APP_CSS" 2>/dev/null; then
                    ok "Fallback réussi : CSS OSEP en place dans $VOL_PATH/build"
                else
                    err "Fallback : le bundle CSS ne contient pas de classes osep-"
                fi
            else
                err "Fallback impossible : volume $VOL_NAME introuvable"
            fi
        else
            err "Fallback impossible : aucun container PHP actif"
        fi
    fi
fi

# =============================================================================
# 6) Permissions runtime sur php / worker
#    IMPORTANT : on itère sur TOUTES les replicas, pas seulement la première.
#    Avec replicas: 3 et update_config: start-first, plusieurs containers peuvent
#    coexister pendant la rolling update. On les fixe tous.
# =============================================================================
log "Application des permissions runtime (${RUNTIME_USER}) sur toutes les replicas..."
TOTAL_FIXED=0
TOTAL_FAILED=0
for SVC in php worker; do
    CIDS=$(docker ps -q --filter "label=com.docker.swarm.service.name=${STACK_NAME}_${SVC}" || true)
    if [ -z "$CIDS" ]; then
        warn "Aucun container actif pour ${STACK_NAME}_${SVC}"
        continue
    fi

    for CID in $CIDS; do
        REPLICA_NAME=$(docker inspect --format '{{.Name}}' "$CID" 2>/dev/null | sed 's|^/||' || echo "$CID")

        if docker exec -i "$CID" sh -lc "
            set -e
            # Création (idempotente) des dossiers runtime
            mkdir -p /var/www/tmp_uploads /var/www/data \
                     /var/www/public /var/www/public/uploads \
                     /var/www/var/cache/prod /var/www/var/log

            # Owner runtime
            chown -R ${RUNTIME_USER}:${RUNTIME_GROUP} \
                /var/www/tmp_uploads \
                /var/www/data \
                /var/www/var \
                /var/www/public/uploads

            # Modes
            chmod 1777 /var/www/tmp_uploads
            chmod -R 775 /var/www/data /var/www/var
            chmod -R 775 /var/www/public/uploads

            # public/ : permissions de lecture stricte (le contenu est piloté
            # par assets_sync, pas par php-fpm)
            find /var/www/public -path /var/www/public/uploads -prune -o -type d -exec chmod 755 {} \; 2>/dev/null || true
            find /var/www/public -path /var/www/public/uploads -prune -o -type f -exec chmod 644 {} \; 2>/dev/null || true
        " 2>/dev/null; then
            TOTAL_FIXED=$((TOTAL_FIXED + 1))
            printf "  ${C_GREEN}✓${C_RESET} %s\n" "$REPLICA_NAME"
        else
            TOTAL_FAILED=$((TOTAL_FAILED + 1))
            printf "  ${C_YELLOW}⚠${C_RESET} %s (échec partiel)\n" "$REPLICA_NAME"
        fi
    done
done
ok "Permissions runtime appliquées sur ${TOTAL_FIXED} replica(s)"
[ "$TOTAL_FAILED" -gt 0 ] && warn "${TOTAL_FAILED} replica(s) ont eu des échecs partiels"

# =============================================================================
# 7) PHP : limits + upload tmp dir
# =============================================================================
log "Configuration PHP (upload limits + tmp dir + logs)..."
PHP_CID=$(docker ps --filter "name=${STACK_NAME}_php" -q | head -n1 || true)
if [ -n "$PHP_CID" ]; then
    docker exec -i "$PHP_CID" sh -lc 'cat > /usr/local/etc/php/conf.d/99-uploads.ini <<EOF
; Limites d upload
upload_max_filesize = 100M
post_max_size = 110M
max_file_uploads = 50
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
upload_tmp_dir = /var/www/tmp_uploads
log_errors = On
error_log = /proc/1/fd/2
open_basedir = /var/www:/tmp
EOF'
    ok "Configuration PHP appliquée"
fi

# =============================================================================
# 8) Mounts idempotents
# =============================================================================
log "Vérification des montages volumes..."

docker service update \
    --mount-add type=volume,src="${STACK_NAME}_data",dst=/var/www/data \
    --mount-add type=volume,src="${STACK_NAME}_tmp_uploads",dst=/var/www/tmp_uploads \
    --force "${STACK_NAME}_php" >/dev/null 2>&1 || true

docker service update \
    --mount-add type=volume,src="${STACK_NAME}_data",dst=/var/www/data \
    --env-add APP_ROLE=worker \
    --force "${STACK_NAME}_worker" >/dev/null 2>&1 || true

docker service update \
    --mount-add type=volume,src="${STACK_NAME}_data",dst=/var/www/data,readonly=true \
    --force "${STACK_NAME}_nginx" >/dev/null 2>&1 || true

# =============================================================================
# 9) Clear du cache Symfony EN TANT QUE www-data
#    (sinon les fichiers de cache recréés appartiennent à root et php-fpm
#     ne peut plus écrire → erreurs 500 "Unable to write in the cache directory")
# =============================================================================
log "Clear cache Symfony (toutes les replicas, en tant que ${RUNTIME_USER})..."
PHP_CIDS=$(docker ps -q --filter "label=com.docker.swarm.service.name=${STACK_NAME}_php")
if [ -n "$PHP_CIDS" ]; then
    for CID in $PHP_CIDS; do
        REPLICA_NAME=$(docker inspect --format '{{.Name}}' "$CID" | sed 's|^/||')

        # Étape A — purge du cache existant en root (pour casser tout fichier
        # root-owned hérité d'un build précédent ou d'une étape antérieure)
        docker exec "$CID" sh -lc '
            rm -rf /var/www/var/cache/prod/* 2>/dev/null || true
            mkdir -p /var/www/var/cache/prod
        ' >/dev/null 2>&1 || true

        # Étape B — re-chown du var/ (les fichiers freshly créés appartiennent à root)
        docker exec "$CID" sh -lc "
            chown -R ${RUNTIME_USER}:${RUNTIME_GROUP} /var/www/var
            chmod -R 775 /var/www/var
        " >/dev/null 2>&1 || true

        # Étape C — cache:clear officiel, en tant que www-data,
        # pour que les fichiers de cache écrits soient owned par www-data dès le départ
        docker exec --user "${RUNTIME_USER}" "$CID" sh -lc '
            php bin/console cache:clear --env=prod --no-warmup --no-debug 2>&1 | tail -2
        ' 2>&1 | sed "s/^/  ${REPLICA_NAME}: /" || warn "Cache clear partiel sur $REPLICA_NAME"

        # Étape D — sécurité finale : on s assure que tout var/ est bien www-data
        # (au cas où le cache:clear aurait recréé des fichiers root)
        docker exec "$CID" sh -lc "
            chown -R ${RUNTIME_USER}:${RUNTIME_GROUP} /var/www/var
            chmod -R 775 /var/www/var
        " >/dev/null 2>&1 || true
    done
    ok "Cache Symfony rafraîchi (permissions garanties)"
else
    warn "Aucun container PHP actif pour le cache:clear"
fi

# =============================================================================
# 10) Reporting final
# =============================================================================
echo
log "État du stack ${STACK_NAME} :"
docker stack services "$STACK_NAME"

echo
ok "Déploiement terminé."

cat <<EOF

${C_DIM}── Vérifications post-déploiement recommandées ─────────────────────${C_RESET}
  1. Test asset CSS  : ${C_DIM}curl -I http://localhost/build/manifest.json${C_RESET}
  2. Test app        : ${C_DIM}curl -I http://localhost/${C_RESET}
  3. Logs nginx      : ${C_DIM}docker service logs ${STACK_NAME}_nginx --tail 20${C_RESET}
  4. Logs PHP        : ${C_DIM}docker service logs ${STACK_NAME}_php --tail 20${C_RESET}
  5. Logs assets     : ${C_DIM}docker service logs ${STACK_NAME}_assets_sync --tail 20${C_RESET}

${C_DIM}── En cas de pépin ─────────────────────────────────────────────────${C_RESET}
  Forcer un nouveau sync des assets :
    ${C_DIM}docker service update --force ${STACK_NAME}_assets_sync${C_RESET}

  Refixer les permissions du cache Symfony (en cas d'erreur 500
  "Unable to write in the cache directory") :
    ${C_DIM}for CID in \$(docker ps -q --filter label=com.docker.swarm.service.name=${STACK_NAME}_php); do
        docker exec "\$CID" sh -lc 'chown -R ${RUNTIME_USER}:${RUNTIME_GROUP} /var/www/var && chmod -R 775 /var/www/var && rm -rf /var/www/var/cache/prod/*'
    done${C_RESET}

  Vérifier le contenu du volume kplan_public :
    ${C_DIM}docker exec \$(docker ps -q --filter name=${STACK_NAME}_nginx | head -1) ls -la /var/www/public/build/ | head -10${C_RESET}

EOF
