#!/usr/bin/env bash
#
# Construit une FIXTURE de `e-learning-prod` — issue #156, livrable B14.
#
# Pourquoi cette fixture existe : aucun dump réel de la production n'est disponible hors
# du serveur, et la répétition de la fenêtre 2 ne peut pas attendre qu'il le soit. La
# fixture reproduit l'état DOCUMENTÉ de la base servie, mesuré deux fois (08/08 puis
# 11/08, identiques) :
#
#   58 tables · MyISAM 58/58 · 0 clé étrangère · 442 colonnes
#   table `migrations` : 62 lignes, MAX(batch) = 5
#   `courses.category_id` ABSENTE alors que sa migration est enregistrée en batch 1
#   6 tables absentes (login_events, lesson_views, quiz_attempts, media_progress,
#     resource_downloads, support_types)
#   `courses.order_no` et `users.is_active` PRÉSENTES, migrations non enregistrées
#   orphelins : lessons→sections 89 (dont lessons→courses 16) · subcategories→categories 1
#     · users→users(parent_id) 5 · enrollments 0/0
#
# COMMENT LE SCHÉMA EST OBTENU — et pourquoi ce n'est pas de l'invention :
#   la production n'a jamais tourné notre git ; son schéma est celui que produisent les
#   62 migrations qu'elle a enregistrées. On les rejoue donc, à l'identique, puis on
#   applique les écarts documentés (`deltas_prod.sql`) et les données (`donnees_prod.sql`).
#
#   Le lot des 62 est DÉRIVÉ, pas recopié : ce sont les migrations jusqu'à la borne
#   ci-dessous, moins les trois que `19_DIFF_SCHEMA_PROD.md` §8 déclare non enregistrées
#   alors que leurs colonnes existent. Contrôle indépendant : ce lot produit
#   57 tables + `migrations` = 58, et 435 + 3 colonnes en trop + 4 colonnes hors
#   migrations = 442. Les deux chiffres tombent juste, ce qui vaut confirmation.
#
# HYPOTHÈSE ASSUMÉE, à corriger dès qu'un vrai dump sera disponible : la répartition des
# 39 lignes antérieures au recalage entre les batchs 1 à 4 n'est documentée que pour une
# seule d'entre elles (`add_category_id_into_courses`, batch 1). La coupure retenue ici
# est chronologique : les 39 plus anciennes d'un côté, les 23 recalées de l'autre. Seuls
# comptent pour la séquence les invariants documentés — 62 lignes, MAX(batch) = 5,
# `category_id` en batch 1 —, et ils sont tous respectés.
#
set -u -o pipefail

# Borne du lot enregistré en production (incluse).
BORNE="2026_05_07_000001_add_answer_comments_and_standalone_fields_to_quizzes.php"
# Migrations dont les objets EXISTENT en production sans que la migration soit
# enregistrée (19_DIFF_SCHEMA_PROD.md §8). Elles sont donc hors du lot.
NON_ENREGISTREES="2026_03_05_144426|2026_03_20_074911|2026_03_20_080000"
# Nombre de lignes recalées en batch 5 le 06/08 (11_AUDIT_PROD_RESULTATS.md).
RECALEES=23

BASE="fixture_e_learning_prod"; SORTIE=""; MYSQL_BIN="mysql"; MYSQLDUMP_BIN=""
PHP_BIN="php"; HOTE="127.0.0.1"; PORT="3306"; UTILISATEUR="root"; RACINE=""; GARDER=0

usage() {
    cat <<'FIN'
Usage : construire_fixture.sh --sortie <fichier.sql> [options]

  --sortie FICHIER     dump produit (obligatoire) — se donne ensuite à
                       bascule_base.sh --dump, exactement comme un mysqldump réel.
  --base NOM           base de travail (défaut : fixture_e_learning_prod), supprimée
                       en fin de course sauf --garder.
  --garder             conserve la base de travail (inspection).
  --mysql-bin / --mysqldump-bin / --php / --host / --port / --user / --racine
FIN
}

while [ $# -gt 0 ]; do
    case "$1" in
        --sortie) SORTIE="$2"; shift 2 ;;
        --base) BASE="$2"; shift 2 ;;
        --garder) GARDER=1; shift ;;
        --mysql-bin) MYSQL_BIN="$2"; shift 2 ;;
        --mysqldump-bin) MYSQLDUMP_BIN="$2"; shift 2 ;;
        --php) PHP_BIN="$2"; shift 2 ;;
        --host) HOTE="$2"; shift 2 ;;
        --port) PORT="$2"; shift 2 ;;
        --user) UTILISATEUR="$2"; shift 2 ;;
        --racine) RACINE="$2"; shift 2 ;;
        -h|--help) usage; exit 0 ;;
        *) echo "Option inconnue : $1" >&2; usage >&2; exit 2 ;;
    esac
done

[ -n "$SORTIE" ] || { echo "ERREUR : --sortie est obligatoire." >&2; usage >&2; exit 2; }

ICI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -n "$RACINE" ] || RACINE="$(cd "$ICI/../../.." && pwd)"
[ -n "$MYSQLDUMP_BIN" ] || MYSQLDUMP_BIN="$(dirname "$(command -v "$MYSQL_BIN" || echo /usr/bin/mysql)")/mysqldump"

echec() { printf '\n!! ÉCHEC — %s\n' "$1" >&2; exit 1; }
info()  { printf '   %s\n' "$1"; }
titre() { printf '\n== %s\n' "$1"; }

sql()      { "$MYSQL_BIN" -h "$HOTE" -P "$PORT" -u "$UTILISATEUR" --protocol=TCP "$@"; }
sql_base() { sql --database="$BASE" "$@"; }
val()      { sql -N --batch -e "$1" | head -1; }

[ -d "$RACINE/database/migrations" ] || echec "racine Laravel invalide : $RACINE"
[ -x "$MYSQLDUMP_BIN" ] || command -v "$MYSQLDUMP_BIN" >/dev/null 2>&1 || echec "mysqldump introuvable : $MYSQLDUMP_BIN"

printf '=== Fixture de e-learning-prod (issue #156) ===\n'
info "base de travail : $BASE sur $HOTE:$PORT"
info "sortie          : $SORTIE"

# ------------------------------------------------------------------ 1. le lot des 62
titre "Sélection des migrations enregistrées en production"
LOT="$(mktemp -d "${TMPDIR:-/tmp}/lot62.XXXXXX")"
ls "$RACINE/database/migrations" | sort | awk -v borne="$BORNE" '{ print } $0 == borne { exit }' \
    | grep -vE "$NON_ENREGISTREES" > "$LOT/liste.txt"
NB="$(wc -l < "$LOT/liste.txt" | tr -d ' ')"
[ "$NB" -eq 62 ] || echec "le lot dérivé compte $NB migrations, 62 attendues — la borne ou les exclusions ont bougé (voir l'en-tête de ce script)"
info "$NB migrations retenues, borne incluse : $BORNE"
while IFS= read -r f; do cp "$RACINE/database/migrations/$f" "$LOT/"; done < "$LOT/liste.txt"

# ------------------------------------------------------------------ 2. schéma
titre "Reconstruction du schéma"
sql -e "DROP DATABASE IF EXISTS \`$BASE\`; CREATE DATABASE \`$BASE\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \
    || echec "création de la base impossible"
( cd "$RACINE" && DB_CONNECTION=mysql DB_HOST="$HOTE" DB_PORT="$PORT" DB_DATABASE="$BASE" \
    DB_USERNAME="$UTILISATEUR" DB_PASSWORD="${MYSQL_PWD:-}" \
    "$PHP_BIN" artisan migrate --path="$LOT" --realpath --force ) > "$LOT/migrate.log" 2>&1 \
    || { tail -20 "$LOT/migrate.log" >&2; echec "rejeu des 62 migrations en échec"; }
info "tables produites : $(val "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE';")"

# ------------------------------------------------------------------ 3. MyISAM
titre "Passage en MyISAM (l'état réel de la production)"
sql -N --batch -e "SELECT DISTINCT CONCAT('ALTER TABLE \`',TABLE_NAME,'\` DROP FOREIGN KEY \`',CONSTRAINT_NAME,'\`;') FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='$BASE' AND REFERENCED_TABLE_NAME IS NOT NULL;" > "$LOT/dropfk.sql"
[ -s "$LOT/dropfk.sql" ] && { sql_base < "$LOT/dropfk.sql" || echec "suppression des clés étrangères en échec"; }
sql -N --batch -e "SELECT CONCAT('ALTER TABLE \`',TABLE_NAME,'\` ENGINE=MyISAM;') FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE' AND TABLE_TYPE='BASE TABLE';" > "$LOT/myisam.sql"
sql_base < "$LOT/myisam.sql" || echec "passage en MyISAM en échec"
info "moteurs : $(sql -N --batch -e "SELECT GROUP_CONCAT(CONCAT(ENGINE,'=',n)) FROM (SELECT ENGINE, COUNT(*) n FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE' GROUP BY ENGINE) t;")"

# ------------------------------------------------------------------ 4. écarts
titre "Application des écarts documentés"
sql_base < "$ICI/deltas_prod.sql" || echec "application de deltas_prod.sql en échec"
info "colonnes : $(val "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$BASE';")"

# ------------------------------------------------------------------ 5. données
titre "Chargement des données"
sql_base < "$ICI/donnees_prod.sql" || echec "chargement de donnees_prod.sql en échec"

# ------------------------------------------------------------------ 6. table migrations
titre "Table migrations : 62 lignes, MAX(batch) = 5"
TOTAL=$((NB))
ANCIENNES=$((TOTAL - RECALEES))
sql_base -e "
    SET @rang := 0;
    UPDATE migrations SET batch = 1;
    UPDATE migrations SET batch = 5 WHERE id > $ANCIENNES;
    UPDATE migrations SET batch = 2 WHERE id = $((ANCIENNES - 2));
    UPDATE migrations SET batch = 3 WHERE id = $((ANCIENNES - 1));
    UPDATE migrations SET batch = 4 WHERE id = $ANCIENNES;
" || echec "réécriture des batchs en échec"
info "$(val "SELECT CONCAT(COUNT(*),' lignes, batch max ',MAX(batch)) FROM \`$BASE\`.migrations;")"
info "category_id enregistrée en batch $(val "SELECT batch FROM \`$BASE\`.migrations WHERE migration LIKE '%add_category_id_into_courses';")"

# ------------------------------------------------------------------ 7. contrôles
titre "Contrôles de conformité au relevé de production"
CTRL=0
verifier() {  # $1 = libellé, $2 = attendu, $3 = mesuré
    if [ "$2" = "$3" ]; then printf '   ✓ %-46s %s\n' "$1" "$3"
    else printf '   ✗ %-46s attendu %s, mesuré %s\n' "$1" "$2" "$3"; CTRL=1; fi
}
verifier "tables" 58 "$(val "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE';")"
verifier "colonnes" 442 "$(val "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$BASE';")"
verifier "tables MyISAM" 58 "$(val "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE' AND ENGINE='MyISAM';")"
verifier "clés étrangères" 0 "$(val "SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='$BASE' AND REFERENCED_TABLE_NAME IS NOT NULL;")"
verifier "lignes de migrations" 62 "$(val "SELECT COUNT(*) FROM \`$BASE\`.migrations;")"
verifier "batch maximum" 5 "$(val "SELECT MAX(batch) FROM \`$BASE\`.migrations;")"
verifier "courses.category_id absente" 0 "$(val "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$BASE' AND TABLE_NAME='courses' AND COLUMN_NAME='category_id';")"
verifier "6 tables de pilotage absentes" 0 "$(val "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$BASE' AND TABLE_NAME IN ('login_events','lesson_views','quiz_attempts','media_progress','resource_downloads','support_types');")"
verifier "utilisateurs" 301 "$(val "SELECT COUNT(*) FROM \`$BASE\`.users;")"
verifier "cours" 54 "$(val "SELECT COUNT(*) FROM \`$BASE\`.courses;")"
verifier "supports" 313 "$(val "SELECT COUNT(*) FROM \`$BASE\`.lessons;")"
verifier "inscriptions" 2128 "$(val "SELECT COUNT(*) FROM \`$BASE\`.enrollments;")"
verifier "complétions" 135 "$(val "SELECT COUNT(*) FROM \`$BASE\`.completed_courses;")"
verifier "orphelins lessons→sections" 89 "$(val "SELECT COUNT(*) FROM \`$BASE\`.lessons l LEFT JOIN \`$BASE\`.sections s ON s.id=l.section_id WHERE l.section_id IS NOT NULL AND s.id IS NULL;")"
verifier "orphelins lessons→courses" 16 "$(val "SELECT COUNT(*) FROM \`$BASE\`.lessons l LEFT JOIN \`$BASE\`.courses c ON c.id=l.course_id WHERE l.course_id IS NOT NULL AND c.id IS NULL;")"
verifier "…tous inclus dans les 89" 16 "$(val "SELECT COUNT(*) FROM \`$BASE\`.lessons l LEFT JOIN \`$BASE\`.courses c ON c.id=l.course_id LEFT JOIN \`$BASE\`.sections s ON s.id=l.section_id WHERE l.course_id IS NOT NULL AND c.id IS NULL AND s.id IS NULL;")"
verifier "orphelins subcategories→categories" 1 "$(val "SELECT COUNT(*) FROM \`$BASE\`.subcategories sc LEFT JOIN \`$BASE\`.categories c ON c.id=sc.category_id WHERE c.id IS NULL;")"
verifier "orphelins users→users(parent_id)" 5 "$(val "SELECT COUNT(*) FROM \`$BASE\`.users u LEFT JOIN \`$BASE\`.users p ON p.id=u.parent_id WHERE u.parent_id IS NOT NULL AND p.id IS NULL;")"
verifier "orphelins enrollments→courses" 0 "$(val "SELECT COUNT(*) FROM \`$BASE\`.enrollments e LEFT JOIN \`$BASE\`.courses c ON c.id=e.course_id WHERE c.id IS NULL;")"
verifier "orphelins enrollments→users" 0 "$(val "SELECT COUNT(*) FROM \`$BASE\`.enrollments e LEFT JOIN \`$BASE\`.users u ON u.id=e.learner_id WHERE u.id IS NULL;")"
verifier "supports orphelins jamais consultés" 0 "$(val "SELECT COUNT(*) FROM \`$BASE\`.learner_lessons ll JOIN \`$BASE\`.lessons l ON l.id=ll.lesson_id LEFT JOIN \`$BASE\`.sections s ON s.id=l.section_id WHERE s.id IS NULL;")"
verifier "réponses de quiz > 125 caractères" 28 "$(val "SELECT COUNT(*) FROM \`$BASE\`.options WHERE CHAR_LENGTH(option_text) > 125;")"
verifier "réponse la plus longue" 295 "$(val "SELECT MAX(CHAR_LENGTH(option_text)) FROM \`$BASE\`.options;")"
verifier "doublons completed_courses" 0 "$(val "SELECT COUNT(*) FROM (SELECT learner_id, course_id FROM \`$BASE\`.completed_courses GROUP BY learner_id, course_id HAVING COUNT(*) > 1) d;")"
[ "$CTRL" -eq 0 ] || echec "la fixture ne reproduit pas l'état documenté de la production"

# ------------------------------------------------------------------ 8. dump
titre "Export"
mkdir -p "$(dirname "$SORTIE")"
"$MYSQLDUMP_BIN" -h "$HOTE" -P "$PORT" -u "$UTILISATEUR" --protocol=TCP \
    --single-transaction --quick --no-tablespaces --skip-add-locks \
    --routines=FALSE --events=FALSE --set-gtid-purged=OFF \
    "$BASE" > "$SORTIE" || echec "mysqldump en échec"
grep -q "Dump completed" "$SORTIE" || echec "le dump ne porte pas la marque « Dump completed »"
info "$(wc -c < "$SORTIE" | tr -d ' ') octets — « Dump completed » présent"

[ "$GARDER" -eq 1 ] || sql -e "DROP DATABASE \`$BASE\`;"
rm -rf "$LOT"
printf '\n=== FIXTURE PRÊTE : %s ===\n' "$SORTIE"
