stages:
  - validate
  - verify_release
  - build
  - deploy
  - performance

variables:
  DOCKER_BUILDKIT: "1"
  COMPOSE_DOCKER_CLI_BUILD: "1"
  GIT_DEPTH: "20"

  DEPLOY_STACK_NAME: "faildaily"
  DEPLOY_STACK_FILE: "docker/docker-stack.ovh-cloud.yml"
  DEPLOY_PORT: "22"
  DEPLOY_PATH: "/home/ubuntu/FailDaily"
  APP_HEALTHCHECK_URL: "https://faildaily.com/api/health/live"

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "web"'

.default_node:
  image: node:20-alpine
  before_script:
    - set -euo pipefail
    - if ! command -v pnpm >/dev/null 2>&1; then corepack enable && corepack prepare pnpm@10.29.3 --activate; fi
    - pnpm --version
    - export TMPDIR=/tmp TMP=/tmp TEMP=/tmp

validate:
  stage: validate
  extends: .default_node
  script:
    - pnpm install --frozen-lockfile
    - pnpm -C backend-api lint
    - pnpm -C frontend lint
    - pnpm -r build
    - NODE_ENV=test DB_DISABLED=true JWT_SECRET=test_jwt_secret_ci SMTP_PASS=fake_smtp_pass OPENAI_API_KEY=fake_openai_key LOGS_DB_PASSWORD=fake_logs_db_pass DB_PASSWORD=fake_db_pass pnpm -C backend-api test:smoke

verify_release_quality:
  stage: verify_release
  extends: .default_node
  script:
    - pnpm install --frozen-lockfile
    - pnpm -C backend-api exec eslint . --ext .js,.cjs,.mjs --max-warnings=30
    - pnpm -C frontend lint
    - pnpm -C frontend exec tsc --noEmit -p tsconfig.app.json
    - pnpm -r build
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

verify_release_backend_tests:
  stage: verify_release
  extends: .default_node
  services:
    - name: mariadb:11.4
      alias: mysql
      command:
        - --character-set-server=utf8mb4
        - --collation-server=utf8mb4_unicode_ci
  variables:
    MYSQL_DATABASE: "faildaily"
    MYSQL_ROOT_PASSWORD: "root"
    NODE_ENV: "test"
    DB_HOST: "mysql"
    DB_PORT: "3306"
    DB_NAME: "faildaily"
    DB_USER: "root"
    DB_PASSWORD: "root"
    LOGS_DB_HOST: "mysql"
    LOGS_DB_PORT: "3306"
    LOGS_DB_NAME: "faildaily"
    LOGS_DB_USER: "root"
    LOGS_DB_PASSWORD: "root"
    JWT_SECRET: "test_jwt_secret_for_ci_min_32_chars"
    SMTP_PASS: "fake_smtp_pass_ci"
    OPENAI_API_KEY: "fake_openai_key_ci"
    QUIET_SECRETS: "true"
    QUIET_LOGS_DB: "true"
    REQUEST_LOGGER_DISABLED: "true"
    # MariaDB 11.4 : MYSQL_ROOT_PASSWORD / MYSQL_DATABASE sont des alias supportés
  script:
    - apk add --no-cache mysql-client bash
    - pnpm install --frozen-lockfile
    - |
      MAX_WAIT=120
      WAITED=0
      until mysqladmin ping -hmysql -uroot -p"$MYSQL_ROOT_PASSWORD" --silent 2>/dev/null; do
        if [ "$WAITED" -ge "$MAX_WAIT" ]; then
          echo "MySQL did not become ready after ${MAX_WAIT}s — aborting"
          exit 1
        fi
        echo "Waiting for MySQL... (${WAITED}s / ${MAX_WAIT}s)"
        sleep 5
        WAITED=$((WAITED + 5))
      done
      echo "MySQL ready after ${WAITED}s"
    - |
      echo "Importing schema: backend-api/migrations/faildaily_bdd.sql"
      mysql -hmysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < backend-api/migrations/faildaily_bdd.sql
    - |
      for f in $(ls -1 backend-api/migrations/*.sql | sort); do
        base="$(basename "$f")"
        if echo "$base" | grep -Eq '^[0-9]{3}_.+\.sql$|^[0-9]{4}-[0-9]{2}-[0-9]{2}_.+\.sql$'; then
          echo "Applying migration: ${base}"
          mysql -hmysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < "$f"
        fi
      done
    - TMPDIR=/tmp TMP=/tmp TEMP=/tmp pnpm -C backend-api test:coverage
  artifacts:
    paths:
      - backend-api/coverage/lcov.info
    expire_in: 1 day
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

verify_release_frontend_tests:
  stage: verify_release
  image: node:20-bookworm
  before_script:
    - set -euo pipefail
    - if ! command -v pnpm >/dev/null 2>&1; then corepack enable && corepack prepare pnpm@10.29.3 --activate; fi
    - pnpm --version
    - export TMPDIR=/tmp TMP=/tmp TEMP=/tmp
  script:
    - apt-get update
    - apt-get install -y chromium
    - pnpm install --frozen-lockfile
    - export CHROME_BIN="$(command -v chromium)"
    - TMPDIR=/tmp TMP=/tmp TEMP=/tmp pnpm -C frontend test --watch=false --browsers=ChromeHeadlessNoSandbox --code-coverage
  artifacts:
    paths:
      - frontend/coverage/app/lcov.info
    expire_in: 1 day
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

verify_release_security:
  stage: verify_release
  extends: .default_node
  script:
    - pnpm install --frozen-lockfile
    - pnpm --dir backend-api audit --audit-level=high --ignore-registry-errors
    - pnpm --dir frontend audit --audit-level=high --ignore-registry-errors
    - if ! command -v rg >/dev/null 2>&1; then apk add --no-cache ripgrep; fi
    - |
      PATTERN='(-----BEGIN (RSA|EC|DSA|OPENSSH|PGP) PRIVATE KEY-----|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|glpat-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|sk_live_[0-9A-Za-z]{20,})'
      if rg --hidden --line-number \
        --glob '!.git/**' --glob '!**/*.md' --glob '!**/.env.example' \
        --glob '!**/package-lock.json' --glob '!**/pnpm-lock.yaml' \
        --glob '!**/environments/environment*.ts' \
        --glob '!**/firebase-messaging-sw.js' \
        --regexp "$PATTERN" .; then
        echo "Potential secret(s) detected in tracked files"
        exit 1
      fi
      echo "Secret scan passed (high-confidence patterns)"
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

sonarqube_analysis:
  stage: verify_release
  needs:
    - verify_release_backend_tests
    - verify_release_frontend_tests
  image:
    name: sonarsource/sonar-scanner-cli:11
    entrypoint: [""]
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"  # Nécessaire pour l'analyse des branches
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - sonar-scanner -Dsonar.token="${SONAR_TOKEN}" -Dsonar.host.url="${SONAR_HOST_URL}"
  allow_failure: true  # Ne bloque pas la release si SonarQube est inaccessible
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

verify_release_ops_readiness:
  stage: verify_release
  image: alpine:3.20
  script:
    - set -euo pipefail
    - apk add --no-cache bash
    - test -n "${APP_HEALTHCHECK_URL:-}" || (echo "APP_HEALTHCHECK_URL is missing" && exit 1)
    - test -f scripts/backup-mysql.sh || (echo "Missing scripts/backup-mysql.sh" && exit 1)
    - test -f scripts/rollback-version.sh || (echo "Missing scripts/rollback-version.sh" && exit 1)
    - test -f BACKUP_ROLLBACK_GUIDE.md || (echo "Missing BACKUP_ROLLBACK_GUIDE.md" && exit 1)
    - bash -n scripts/backup-mysql.sh
    - bash -n scripts/rollback-version.sh
    - echo "Ops readiness checks passed (backup + rollback assets present and valid)"
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

build_and_push_images:
  stage: build
  needs:
    - validate
    - verify_release_quality
    - verify_release_backend_tests
    - verify_release_security
    - verify_release_ops_readiness
  image:
    name: gcr.io/kaniko-project/executor:v1.23.2-debug
    entrypoint: [""]
  script:
    - set -euo pipefail
    # GitLab injecte automatiquement CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD
    # On les utilise en priorité — ils ont toujours les droits complets sur le registre du projet
    - EFFECTIVE_REGISTRY="${CI_REGISTRY:-${REGISTRY_HOST:-}}"
    - OVH_REGISTRY_USER="${CI_REGISTRY_USER:-${REGISTRY_USER:-${OVH_REGISTRY_USER:-}}}"
    - OVH_REGISTRY_PASSWORD="${CI_REGISTRY_PASSWORD:-${REGISTRY_PASSWORD:-${OVH_REGISTRY_PASSWORD:-}}}"
    # CI_REGISTRY_IMAGE est injecté par GitLab avec le bon namespace (ex: registry.host/root/faildaily)
    - BASE_REPO="${CI_REGISTRY_IMAGE:-${EFFECTIVE_REGISTRY}/faildaily}"
    - |
      if [ -n "${IMAGE_REPOSITORY:-}" ]; then
        BACKEND_IMAGE_REPO="${BACKEND_IMAGE_REPO:-${IMAGE_REPOSITORY}-backend}"
        FRONTEND_IMAGE_REPO="${FRONTEND_IMAGE_REPO:-${IMAGE_REPOSITORY}-frontend}"
        ADMIN_FRONTEND_IMAGE_REPO="${ADMIN_FRONTEND_IMAGE_REPO:-${IMAGE_REPOSITORY}-admin-frontend}"
        MODERATION_FRONTEND_IMAGE_REPO="${MODERATION_FRONTEND_IMAGE_REPO:-${IMAGE_REPOSITORY}-moderation-frontend}"
        MYSQL_IMAGE_REPO="${MYSQL_IMAGE_REPO:-${IMAGE_REPOSITORY}-mysql}"
      else
        BACKEND_IMAGE_REPO="${BACKEND_IMAGE_REPO:-${BASE_REPO}/faildaily-backend}"
        FRONTEND_IMAGE_REPO="${FRONTEND_IMAGE_REPO:-${BASE_REPO}/faildaily-frontend}"
        ADMIN_FRONTEND_IMAGE_REPO="${ADMIN_FRONTEND_IMAGE_REPO:-${BASE_REPO}/faildaily-admin-frontend}"
        MODERATION_FRONTEND_IMAGE_REPO="${MODERATION_FRONTEND_IMAGE_REPO:-${BASE_REPO}/faildaily-moderation-frontend}"
        MYSQL_IMAGE_REPO="${MYSQL_IMAGE_REPO:-${BASE_REPO}/faildaily-mysql}"
      fi
    - test -n "${EFFECTIVE_REGISTRY:-}" || (echo "Missing CI variable CI_REGISTRY or REGISTRY_HOST" && exit 1)
    - test -n "${OVH_REGISTRY_USER:-}" || (echo "Missing CI variable CI_REGISTRY_USER or REGISTRY_USER" && exit 1)
    - test -n "${OVH_REGISTRY_PASSWORD:-}" || (echo "Missing CI variable CI_REGISTRY_PASSWORD or REGISTRY_PASSWORD" && exit 1)
    - IMAGE_TAG="${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
    - BACKEND_IMAGE="${BACKEND_IMAGE_REPO}:${IMAGE_TAG}"
    - FRONTEND_IMAGE="${FRONTEND_IMAGE_REPO}:${IMAGE_TAG}"
    - ADMIN_FRONTEND_IMAGE="${ADMIN_FRONTEND_IMAGE_REPO}:${IMAGE_TAG}"
    - MYSQL_IMAGE="${MYSQL_IMAGE_REPO}:${IMAGE_TAG}"
    - mkdir -p /kaniko/.docker
    - printf '{"auths":{"%s":{"username":"%s","password":"%s"}}}\n' "$EFFECTIVE_REGISTRY" "$OVH_REGISTRY_USER" "$OVH_REGISTRY_PASSWORD" > /kaniko/.docker/config.json
    - BACKEND_DEST="--destination ${BACKEND_IMAGE} --destination ${BACKEND_IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}"
    - FRONTEND_DEST="--destination ${FRONTEND_IMAGE} --destination ${FRONTEND_IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}"
    - ADMIN_DEST="--destination ${ADMIN_FRONTEND_IMAGE} --destination ${ADMIN_FRONTEND_IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}"
    - MYSQL_DEST="--destination ${MYSQL_IMAGE} --destination ${MYSQL_IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}"
    - |
      if [ "${CI_COMMIT_BRANCH:-}" = "$CI_DEFAULT_BRANCH" ]; then
        BACKEND_DEST="${BACKEND_DEST} --destination ${BACKEND_IMAGE_REPO}:latest"
        FRONTEND_DEST="${FRONTEND_DEST} --destination ${FRONTEND_IMAGE_REPO}:latest"
        ADMIN_DEST="${ADMIN_DEST} --destination ${ADMIN_FRONTEND_IMAGE_REPO}:latest"
        MYSQL_DEST="${MYSQL_DEST} --destination ${MYSQL_IMAGE_REPO}:latest"
      fi
    - /kaniko/executor --context "${CI_PROJECT_DIR}/backend-api" --dockerfile "${CI_PROJECT_DIR}/backend-api/Dockerfile" ${BACKEND_DEST}
    - /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/docker/frontend.Dockerfile" ${FRONTEND_DEST}
    - /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/docker/admin-frontend.Dockerfile" ${ADMIN_DEST}
    # Image MySQL avec dump prod intégré (initdb.d) — contexte racine pour accéder aux migrations
    # --ignore-path : contourne les symlinks circulaires dans l'image mysql:8.4 (bug Kaniko connu)
    - /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/docker/mysql.Dockerfile" --ignore-path /var/spool/mail --ignore-path /var/mail ${MYSQL_DEST}
    - printf "BACKEND_IMAGE=%s\nFRONTEND_IMAGE=%s\nADMIN_FRONTEND_IMAGE=%s\nMYSQL_IMAGE=%s\n" "$BACKEND_IMAGE" "$FRONTEND_IMAGE" "$ADMIN_FRONTEND_IMAGE" "$MYSQL_IMAGE" > build-images.env
  artifacts:
    paths:
      - build-images.env
    expire_in: 1 day
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

# Job séparé pour l'image modération — token GitLab frais, évite l'UNAUTHORIZED
# sur un nouveau dépôt de registre créé pour la première fois.
build_moderation_image:
  stage: build
  needs:
    - validate
    - verify_release_quality
    - verify_release_backend_tests
    - verify_release_security
    - verify_release_ops_readiness
  image:
    name: gcr.io/kaniko-project/executor:v1.23.2-debug
    entrypoint: [""]
  script:
    - set -euo pipefail
    - EFFECTIVE_REGISTRY="${CI_REGISTRY:-${REGISTRY_HOST:-}}"
    # Priorité aux credentials réels (REGISTRY_USER/PASSWORD) — le job token
    # gitlab-ci-token ne peut pas créer un dépôt registre qui n'existe pas encore.
    # Les credentials réels sont des comptes admin qui peuvent créer de nouveaux dépôts.
    - OVH_REGISTRY_USER="${REGISTRY_USER:-${OVH_REGISTRY_USER:-${CI_REGISTRY_USER:-}}}"
    - OVH_REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-${OVH_REGISTRY_PASSWORD:-${CI_REGISTRY_PASSWORD:-}}}"
    # CI_REGISTRY_IMAGE contient déjà le bon namespace (ex: registry.host/root/faildaily)
    - BASE_REPO="${CI_REGISTRY_IMAGE:-${EFFECTIVE_REGISTRY}/faildaily}"
    - |
      if [ -n "${IMAGE_REPOSITORY:-}" ]; then
        MODERATION_FRONTEND_IMAGE_REPO="${MODERATION_FRONTEND_IMAGE_REPO:-${IMAGE_REPOSITORY}-moderation-frontend}"
      else
        MODERATION_FRONTEND_IMAGE_REPO="${MODERATION_FRONTEND_IMAGE_REPO:-${BASE_REPO}/faildaily-moderation-frontend}"
      fi
    - test -n "${EFFECTIVE_REGISTRY:-}" || (echo "Missing CI variable CI_REGISTRY or REGISTRY_HOST" && exit 1)
    - test -n "${OVH_REGISTRY_USER:-}" || (echo "Missing CI variable CI_REGISTRY_USER or REGISTRY_USER" && exit 1)
    - test -n "${OVH_REGISTRY_PASSWORD:-}" || (echo "Missing CI variable CI_REGISTRY_PASSWORD or REGISTRY_PASSWORD" && exit 1)
    - IMAGE_TAG="${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
    - MODERATION_FRONTEND_IMAGE="${MODERATION_FRONTEND_IMAGE_REPO}:${IMAGE_TAG}"
    - mkdir -p /kaniko/.docker
    - printf '{"auths":{"%s":{"username":"%s","password":"%s"}}}\n' "$EFFECTIVE_REGISTRY" "$OVH_REGISTRY_USER" "$OVH_REGISTRY_PASSWORD" > /kaniko/.docker/config.json
    - MODERATION_DEST="--destination ${MODERATION_FRONTEND_IMAGE} --destination ${MODERATION_FRONTEND_IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}"
    - |
      if [ "${CI_COMMIT_BRANCH:-}" = "$CI_DEFAULT_BRANCH" ]; then
        MODERATION_DEST="${MODERATION_DEST} --destination ${MODERATION_FRONTEND_IMAGE_REPO}:latest"
      fi
    - /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/docker/moderation.Dockerfile" ${MODERATION_DEST}
    - printf "MODERATION_FRONTEND_IMAGE=%s\n" "$MODERATION_FRONTEND_IMAGE" > moderation-image.env
  artifacts:
    paths:
      - moderation-image.env
    expire_in: 1 day
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
    - if: "$CI_COMMIT_TAG"

deploy_production:
  stage: deploy
  tags:
    - local
  needs:
    - build_and_push_images
    - build_moderation_image
  image: alpine:3.20
  before_script:
    - apk add --no-cache bash openssh-client curl
  environment:
    name: production
    url: https://faildaily.com
  script:
    - set -euo pipefail
    - . build-images.env
    - . moderation-image.env
    - DEPLOY_PORT="${DEPLOY_PORT:-${OVH_SSH_PORT:-22}}"
    - EFFECTIVE_REGISTRY="${CI_REGISTRY:-${REGISTRY_HOST:-}}"
    - DEPLOY_HOST="${DEPLOY_HOST:-${OVH_SSH_HOST:-}}"
    - DEPLOY_USER="${DEPLOY_USER:-${OVH_SSH_USER:-}}"
    - OVH_REGISTRY_USER="${CI_REGISTRY_USER:-${REGISTRY_USER:-${OVH_REGISTRY_USER:-}}}"
    - OVH_REGISTRY_PASSWORD="${CI_REGISTRY_PASSWORD:-${REGISTRY_PASSWORD:-${OVH_REGISTRY_PASSWORD:-}}}"
    - |
      if [ -z "${SSH_PRIVATE_KEY:-}" ]; then
        if [ -n "${OVH_SSH_PRIVATE_KEY_B64:-}" ]; then
          SSH_PRIVATE_KEY="$(printf '%s' "$OVH_SSH_PRIVATE_KEY_B64" | base64 -d)"
        elif [ -n "${OVH_SSH_PRIVATE_KEY_B6:-}" ]; then
          SSH_PRIVATE_KEY="$(printf '%s' "$OVH_SSH_PRIVATE_KEY_B6" | base64 -d)"
        fi
      fi
    - test -n "${DEPLOY_HOST:-}" || (echo "Missing CI variable DEPLOY_HOST or OVH_SSH_HOST" && exit 1)
    - test -n "${DEPLOY_USER:-}" || (echo "Missing CI variable DEPLOY_USER or OVH_SSH_USER" && exit 1)
    - test -n "${SSH_PRIVATE_KEY:-}" || (echo "Missing CI variable SSH_PRIVATE_KEY or OVH_SSH_PRIVATE_KEY_B64" && exit 1)
    - test -n "${EFFECTIVE_REGISTRY:-}" || (echo "Missing CI variable CI_REGISTRY or REGISTRY_HOST" && exit 1)
    - test -n "${OVH_REGISTRY_USER:-}" || (echo "Missing CI variable OVH_REGISTRY_USER or REGISTRY_USER" && exit 1)
    - test -n "${OVH_REGISTRY_PASSWORD:-}" || (echo "Missing CI variable OVH_REGISTRY_PASSWORD or REGISTRY_PASSWORD" && exit 1)
    - eval "$(ssh-agent -s)"
    - printf '%s\n' "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - |
      if [ -n "${OVH_SSH_KNOWN_HOSTS_B64:-}" ]; then
        printf '%s' "$OVH_SSH_KNOWN_HOSTS_B64" | base64 -d >> ~/.ssh/known_hosts
      elif [ -n "${OVH_SSH_KNOWN_HOSTS_B6:-}" ]; then
        printf '%s' "$OVH_SSH_KNOWN_HOSTS_B6" | base64 -d >> ~/.ssh/known_hosts
      else
        echo "Falling back to ssh-keyscan..."
        ssh-keyscan -T 30 -p "$DEPLOY_PORT" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>&1 || {
          echo "ERROR: ssh-keyscan failed — VPS unreachable on port $DEPLOY_PORT"
          exit 1
        }
      fi
    - |
      if [ ! -s ~/.ssh/known_hosts ]; then
        echo "ERROR: known_hosts is empty"
        exit 1
      fi
    - chmod 600 ~/.ssh/known_hosts
    - STACK_REMOTE_FILE="/tmp/${DEPLOY_STACK_NAME}-stack-${CI_PIPELINE_ID}.yml"
    - ENV_REMOTE_FILE="/tmp/${DEPLOY_STACK_NAME}-deploy-${CI_PIPELINE_ID}.env"
    - scp -P "$DEPLOY_PORT" "$DEPLOY_STACK_FILE" "${DEPLOY_USER}@${DEPLOY_HOST}:${STACK_REMOTE_FILE}"
    - |
      cat > deploy-remote.env <<EOF
      REGISTRY_HOST=${EFFECTIVE_REGISTRY}
      OVH_REGISTRY_USER=${OVH_REGISTRY_USER}
      OVH_REGISTRY_PASSWORD=${OVH_REGISTRY_PASSWORD}
      BACKEND_IMAGE=${BACKEND_IMAGE}
      FRONTEND_IMAGE=${FRONTEND_IMAGE}
      ADMIN_FRONTEND_IMAGE=${ADMIN_FRONTEND_IMAGE}
      MODERATION_FRONTEND_IMAGE=${MODERATION_FRONTEND_IMAGE}
      MYSQL_IMAGE=${MYSQL_IMAGE}
      DEPLOY_STACK_NAME=${DEPLOY_STACK_NAME}
      STACK_REMOTE_FILE=${STACK_REMOTE_FILE}
      DEPLOY_PATH=${DEPLOY_PATH}
      EOF
    - scp -P "$DEPLOY_PORT" deploy-remote.env "${DEPLOY_USER}@${DEPLOY_HOST}:${ENV_REMOTE_FILE}"
    - |
      ssh -p "$DEPLOY_PORT" "${DEPLOY_USER}@${DEPLOY_HOST}" "ENV_REMOTE_FILE='${ENV_REMOTE_FILE}' bash -se" <<'EOSSH'
      set -euo pipefail
      set -a
      source "$ENV_REMOTE_FILE"
      set +a

      if [ -n "${DEPLOY_PATH:-}" ] && [ -d "$DEPLOY_PATH" ]; then
        cd "$DEPLOY_PATH"
      fi

      echo "$OVH_REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "$OVH_REGISTRY_USER" --password-stdin

      docker stack deploy -c "$STACK_REMOTE_FILE" "$DEPLOY_STACK_NAME" --with-registry-auth --prune
      docker stack services "$DEPLOY_STACK_NAME"
      docker stack ps "$DEPLOY_STACK_NAME" --filter desired-state=running

      rm -f "$STACK_REMOTE_FILE" "$ENV_REMOTE_FILE"
      EOSSH
    - rm -f deploy-remote.env
    - |
      RETRY=0
      MAX_RETRY=18
      until curl -fsS "$APP_HEALTHCHECK_URL" >/dev/null || [ "$RETRY" -eq "$MAX_RETRY" ]; do
        RETRY=$((RETRY + 1))
        echo "Health check pending: $RETRY/$MAX_RETRY"
        sleep 10
      done
      if [ "$RETRY" -eq "$MAX_RETRY" ]; then
        echo "Health check failed: $APP_HEALTHCHECK_URL"
        exit 1
      fi
      echo "Health check OK: $APP_HEALTHCHECK_URL"
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"

# ── Stage performance ─────────────────────────────────────────────────────────
# Test de charge minimal post-déploiement sur les endpoints critiques.
# Déclenchement MANUEL uniquement (when: manual) pour ne pas bloquer la release.
# Objectifs : p(95) < 500 ms, taux d'erreur < 1 % sous 20 VU pendant 1 min.
# Variables surchargeables : LOAD_TEST_BASE_URL (défaut: https://faildaily.com)
load_test:
  stage: performance
  image:
    name: grafana/k6:latest
    entrypoint: [""]
  needs:
    - deploy_production
  variables:
    LOAD_TEST_BASE_URL: "https://faildaily.com"
  script:
    - k6 run --env BASE_URL="${LOAD_TEST_BASE_URL}" backend-api/tests/k6/load-test.js
  rules:
    - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
      when: manual
      allow_failure: true
