#!/usr/bin/env bash
# =============================================================================
# init-stack.sh — Initialisation complète de la stack ClickHouse pour ja4-platform
#
# Ce script exécute l'ensemble du schéma SQL, charge les données CSV de
# référence et vérifie que tous les composants sont opérationnels.
# Il est utilisé par les tests d'intégration et pour la mise en place de
# l'environnement de développement.
#
# Usage:
#   ./scripts/init-stack.sh                         # init dev stack
#   ./scripts/init-stack.sh --container my-ch-1     # conteneur spécifique
#   ./scripts/init-stack.sh --user admin --pass X    # credentials spécifiques
#   ./scripts/init-stack.sh --import-prod            # init + import données prod
#   ./scripts/init-stack.sh --reset                  # DROP databases, recréer tout
#
# Variables d'environnement :
#   DEV_CONTAINER       Nom du conteneur ClickHouse (défaut: integration-clickhouse-1)
#   DEV_USER            Utilisateur ClickHouse (défaut: default)
#   DEV_PASSWORD        Mot de passe ClickHouse (défaut: vide)
#   CLICKHOUSE_DB_LOGS  Base de données logs (défaut: ja4_logs)
#   CLICKHOUSE_DB_PROC  Base de données processing (défaut: ja4_processing)
# =============================================================================
set -euo pipefail

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

# ── Configuration ────────────────────────────────────────────────────────────
DEV_CONTAINER="${DEV_CONTAINER:-integration-clickhouse-1}"
DEV_USER="${DEV_USER:-default}"
DEV_PASSWORD="${DEV_PASSWORD:-}"
DB_LOGS="${CLICKHOUSE_DB_LOGS:-ja4_logs}"
DB_PROC="${CLICKHOUSE_DB_PROC:-ja4_processing}"
IMPORT_PROD=false
RESET=false

# ── Parsing des arguments ────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
    case "$1" in
        --container)   DEV_CONTAINER="$2"; shift 2 ;;
        --user)        DEV_USER="$2";      shift 2 ;;
        --pass)        DEV_PASSWORD="$2";  shift 2 ;;
        --import-prod) IMPORT_PROD=true;   shift ;;
        --reset)       RESET=true;         shift ;;
        -h|--help)
            sed -n '2,/^# =====/{ /^# =====/d; s/^# \?//p; }' "$0"
            exit 0
            ;;
        *) echo "Option inconnue : $1"; exit 1 ;;
    esac
done

SQL_DIR="${REPO_ROOT}/shared/clickhouse"

# ── Couleurs ─────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log()  { echo -e "${CYAN}[init]${NC} $(date '+%H:%M:%S') $*"; }
ok()   { echo -e "${GREEN}  ✓ $*${NC}"; }
warn() { echo -e "${YELLOW}  ⚠ $*${NC}"; }
err()  { echo -e "${RED}  ✗ $*${NC}" >&2; exit 1; }

# ── Requêteur CH ─────────────────────────────────────────────────────────────
ch() {
    local args=("--query" "$1")
    if [[ -n "${DEV_PASSWORD}" ]]; then
        args+=("--user" "${DEV_USER}" "--password" "${DEV_PASSWORD}")
    fi
    docker exec -i "${DEV_CONTAINER}" clickhouse-client "${args[@]}"
}

ch_multiquery() {
    local args=("--multiquery")
    if [[ -n "${DEV_PASSWORD}" ]]; then
        args+=("--user" "${DEV_USER}" "--password" "${DEV_PASSWORD}")
    fi
    docker exec -i "${DEV_CONTAINER}" clickhouse-client "${args[@]}" <<< "$1"
}

ch_insert_native() {
    # $1 = table, stdin = Native data
    local args=("--query" "INSERT INTO $1 FORMAT Native")
    if [[ -n "${DEV_PASSWORD}" ]]; then
        args+=("--user" "${DEV_USER}" "--password" "${DEV_PASSWORD}")
    fi
    docker exec -i "${DEV_CONTAINER}" clickhouse-client "${args[@]}"
}

# ── Vérification du conteneur ────────────────────────────────────────────────
log "Vérification du conteneur ${DEV_CONTAINER}…"
if ! docker exec "${DEV_CONTAINER}" clickhouse-client --query "SELECT 1" > /dev/null 2>&1; then
    err "Le conteneur ${DEV_CONTAINER} n'est pas accessible"
fi
ok "Conteneur ${DEV_CONTAINER} accessible"

# ── Reset optionnel ──────────────────────────────────────────────────────────
if [ "${RESET}" = true ]; then
    log "Reset demandé — suppression des bases de données…"
    ch "DROP DATABASE IF EXISTS ${DB_LOGS}" 2>/dev/null || true
    ch "DROP DATABASE IF EXISTS ${DB_PROC}" 2>/dev/null || true
    ok "Bases ${DB_LOGS} et ${DB_PROC} supprimées"
fi

# ── Exécution des fichiers SQL ───────────────────────────────────────────────
# Phase 1 : bases + tables persistantes + dictionnaires + MVs primaires (00-04)
# Phase 2 : drop/recreate des tables dérivées (agg_*, ml_*) pour garantir le schéma
# Phase 3 : tables dérivées + vues (05-12)
# Cette approche garantit que les vues (07+) trouvent toutes les colonnes attendues,
# même si le schéma a évolué depuis la dernière initialisation.

SQL_PHASE1=(
    00_database.sql
    01_raw_tables.sql
    02_dictionaries.sql
    03_anubis_tables.sql
    04_mv_http_logs.sql
)

SQL_PHASE3=(
    05_aggregation_tables.sql
    06_ml_tables.sql
    07_ai_features_view.sql
    08_users.sql
    09_audit_table.sql
    10_perf_indexes.sql
    11_views.sql
    12_thesis_features.sql
)

ALL_SQL=("${SQL_PHASE1[@]}" "${SQL_PHASE3[@]}")
log "Application du schéma SQL (${#ALL_SQL[@]} fichiers)…"
ERRORS=0

# Fonction commune d'exécution SQL avec substitution
run_sql_file() {
    local f="$1"
    local filepath="${SQL_DIR}/${f}"
    if [[ ! -f "${filepath}" ]]; then
        echo "  WARN: ${f} non trouvé, ignoré" >&2
        return 0
    fi
    local SQL_PATCHED
    SQL_PATCHED=$(sed \
        -e "s/ja4_logs/${DB_LOGS}/g" \
        -e "s/ja4_processing/${DB_PROC}/g" \
        -e "s/USER 'admin'/USER '${DEV_USER}'/g" \
        -e "s/PASSWORD 'CHANGE_ME'/PASSWORD '${DEV_PASSWORD}'/g" \
        -e "s/PASSWORD 'ChangeMe'/PASSWORD '${DEV_PASSWORD}'/g" \
        "${filepath}")

    if [[ "${f}" == 10_* ]]; then
        if ch_multiquery "${SQL_PATCHED}" 2>/dev/null; then
            ok "${f}"
        else
            echo "  ⚠ ${f} (erreurs ignorées — index déjà existants)"
        fi
    else
        if ch_multiquery "${SQL_PATCHED}" 2>/dev/null; then
            ok "${f}"
        else
            echo "  ✗ ${f} — ERREUR" >&2
            ERRORS=$((ERRORS + 1))
        fi
    fi
}

# Pré-Phase 1 : drop mv_http_logs + http_logs pour garantir la recréation avec le
# schéma complet (y compris les colonnes h2_* ajoutées à l'étape 1).
# Ces tables sont vides pendant le développement/test — safe to drop.
ch "DROP VIEW IF EXISTS ${DB_LOGS}.mv_http_logs"   2>/dev/null || true
ch "DROP TABLE IF EXISTS ${DB_LOGS}.http_logs"      2>/dev/null || true

# Phase 1 : bases, tables persistantes, dictionnaires
for f in "${SQL_PHASE1[@]}"; do
    run_sql_file "${f}"
done

# Phase 2 : drop tables dérivées (repopulées automatiquement par les MVs)
# Ceci garantit que 05/06/12 recréent les tables avec TOUTES les colonnes du
# schéma actuel, même si une version antérieure était déjà déployée.
log "Nettoyage des tables dérivées (agg_*, ml_*) pour garantir le schéma…"
DERIVED_MVS=(
    "${DB_PROC}.mv_agg_host_ip_ja4_1h"
    "${DB_PROC}.mv_agg_header_fingerprint_1h"
    "${DB_PROC}.mv_agg_path_sequences_1h"
    "${DB_PROC}.mv_agg_request_timing_1h"
    "${DB_PROC}.mv_agg_ip_behavior_1h"
    "${DB_PROC}.mv_agg_resource_cascade_1h"
)
DERIVED_TABLES=(
    "${DB_PROC}.agg_host_ip_ja4_1h"
    "${DB_PROC}.agg_header_fingerprint_1h"
    "${DB_PROC}.agg_path_sequences_1h"
    "${DB_PROC}.agg_request_timing_1h"
    "${DB_PROC}.agg_ip_behavior_1h"
    "${DB_PROC}.agg_resource_cascade_1h"
    "${DB_PROC}.ml_detected_anomalies"
    "${DB_PROC}.ml_all_scores"
)
for mv in "${DERIVED_MVS[@]}"; do
    ch "DROP VIEW IF EXISTS ${mv}" 2>/dev/null || true
done
for tbl in "${DERIVED_TABLES[@]}"; do
    ch "DROP TABLE IF EXISTS ${tbl}" 2>/dev/null || true
done
ok "Tables dérivées nettoyées"

# Phase 3 : tables dérivées + vues (schéma complet garanti)
for f in "${SQL_PHASE3[@]}"; do
    run_sql_file "${f}"
done

if [ "${ERRORS}" -gt 0 ]; then
    err "${ERRORS} fichier(s) SQL en erreur"
fi

# ── Nettoyage post-schéma (tables Anubis obsolètes) ──────────────────────────
# Note : les migrations inline ne sont plus nécessaires — les tables dérivées
# sont DROP+CREATE en phase 2/3, garantissant le schéma complet.
log "Nettoyage des tables Anubis obsolètes…"
ch "DROP DICTIONARY IF EXISTS ${DB_PROC}.dict_anubis_ua"    2>/dev/null || true
ch "DROP DICTIONARY IF EXISTS ${DB_PROC}.dict_anubis_country" 2>/dev/null || true
ch "DROP TABLE IF EXISTS ${DB_PROC}.anubis_ua_rules"        2>/dev/null || true
ch "DROP TABLE IF EXISTS ${DB_PROC}.anubis_country_rules"   2>/dev/null || true
ok "Tables obsolètes supprimées"

# ── Migrations additionnelles (fleet_detections, ml_performance_metrics) ─────
log "Application des migrations additionnelles…"
MIGRATIONS_DIR="${SCRIPT_DIR}/../services/correlator/sql/migrations"
if [ -d "${MIGRATIONS_DIR}" ]; then
    for mig in "${MIGRATIONS_DIR}"/0[0-9]_*.sql; do
        [ -f "${mig}" ] || continue
        fname=$(basename "${mig}")
        SQL_MIG=$(sed \
            -e "s/ja4_logs/${DB_LOGS}/g" \
            -e "s/ja4_processing/${DB_PROC}/g" \
            "${mig}")
        if ch_multiquery "${SQL_MIG}" 2>/dev/null; then
            ok "  migration/${fname}"
        else
            # Réessayer pour capturer l'erreur réelle
            mig_err=$(ch_multiquery "${SQL_MIG}" 2>&1 || true)
            if echo "${mig_err}" | grep -qiE "ALREADY_EXISTS|already exists|Object .* already exists"; then
                ok "  migration/${fname} (déjà existant)"
            else
                warn "  migration/${fname} — ${mig_err}"
            fi
        fi
    done
else
    warn "Répertoire migrations introuvable : ${MIGRATIONS_DIR}"
fi

# ── Vérification du schéma ───────────────────────────────────────────────────
log "Vérification du schéma…"

TABLE_COUNT=$(ch "SELECT count() FROM system.tables WHERE database IN ('${DB_LOGS}','${DB_PROC}')")
DICT_COUNT=$(ch "SELECT count() FROM system.dictionaries WHERE database='${DB_PROC}'")
VIEW_COUNT=$(ch "SELECT count() FROM system.tables WHERE database='${DB_PROC}' AND engine='View'")
MV_COUNT=$(ch "SELECT count() FROM system.tables WHERE database IN ('${DB_LOGS}','${DB_PROC}') AND engine='MaterializedView'")

ok "Tables: ${TABLE_COUNT} | Dictionnaires: ${DICT_COUNT} | Vues: ${VIEW_COUNT} | MVs: ${MV_COUNT}"

# Vérification des tables critiques
CRITICAL_TABLES=(
    "${DB_LOGS}.http_logs_raw"
    "${DB_LOGS}.http_logs"
    "${DB_PROC}.ml_detected_anomalies"
    "${DB_PROC}.ml_all_scores"
    "${DB_PROC}.agg_host_ip_ja4_1h"
    "${DB_PROC}.anubis_ip_rules"
    "${DB_PROC}.anubis_asn_rules"
    "${DB_PROC}.fleet_detections"
    "${DB_PROC}.ml_performance_metrics"
)
for t in "${CRITICAL_TABLES[@]}"; do
    db="${t%%.*}"
    tbl="${t##*.}"
    EXISTS=$(ch "SELECT count() FROM system.tables WHERE database='${db}' AND name='${tbl}'" 2>/dev/null || echo "0")
    if [ "${EXISTS}" = "1" ]; then
        ok "  ${t}"
    else
        err "  Table manquante : ${t}"
    fi
done

# Vérification des dictionnaires critiques
CRITICAL_DICTS=(
    "dict_anubis_ip"
    "dict_anubis_asn"
    "dict_iplocate_asn"
    "dict_bot_ip"
    "dict_bot_ja4"
    "dict_browser_ja4"
    "dict_asn_reputation"
)
for d in "${CRITICAL_DICTS[@]}"; do
    STATUS=$(ch "SELECT status FROM system.dictionaries WHERE database='${DB_PROC}' AND name='${d}'" 2>/dev/null || echo "MISSING")
    if [ "${STATUS}" = "LOADED" ] || [ "${STATUS}" = "NOT_LOADED" ]; then
        ok "  ${d} (${STATUS})"
    else
        echo "  ⚠ Dictionnaire ${d}: ${STATUS}"
    fi
done

# ── Import des données prod (optionnel) ──────────────────────────────────────
if [ "${IMPORT_PROD}" = true ]; then
    IMPORT_SCRIPT="${SCRIPT_DIR}/import-prod-data.sh"
    if [[ -x "${IMPORT_SCRIPT}" ]]; then
        log "Lancement de l'import des données prod…"
        "${IMPORT_SCRIPT}" --container "${DEV_CONTAINER}"
    else
        echo "  ⚠ Script d'import non trouvé : ${IMPORT_SCRIPT}"
    fi
fi

# ── Résultat ─────────────────────────────────────────────────────────────────
log "════════════════════════════════════════════════════"
log "  Initialisation terminée"
log "  Bases : ${DB_LOGS}, ${DB_PROC}"
log "  Tables: ${TABLE_COUNT} | Dicts: ${DICT_COUNT} | MVs: ${MV_COUNT}"
log "════════════════════════════════════════════════════"
