#!/usr/bin/env bash
# Backfill whp.user_databases and whp.database_users for cpanel imports that
# completed BEFORE the importer started writing those tracking rows itself.
#
# Background
#   The cPanel import path (libs/CpanelBackupImporter.php) creates databases
#   and database users at the MySQL layer (CREATE DATABASE, CREATE USER, GRANT)
#   but historically did not register them in whp.user_databases /
#   whp.database_users. The panel's Database Management UI reads
#   information_schema directly for the listing, so DBs DID appear -- but
#   joins through the tracking tables (size sweeps, audit lookups, future
#   ownership tooling) saw no rows for imported DBs.
#
#   The forward fix lives in libs/CpanelBackupImporter.php (recordUserDatabase
#   + recordDatabaseUser, plus a `mysql_users` key in the summary writer).
#   This script reconstructs the same rows for imports that completed before
#   the fix shipped.
#
# Inputs
#   /var/lib/whp/cpanel-imports/<import_id>.json    one per completed import
#   whp.cpanel_imports                              join: import_id -> username
#
# Output
#   /var/log/whp/cpanel-import-db-tracking-backfill-<UTC-timestamp>.log
#   stdout: per-import + summary lines (also tee'd to the log file)
#
# Safety
#   - Idempotent. All INSERTs use ON DUPLICATE KEY UPDATE -- re-running adds
#     nothing.
#   - Owner cross-check. For each DB we cross-reference the cpanel_imports
#     row's username against the username embedded in the renamed DB name
#     (`<owner>_<originalname>_<hash>`). A mismatch is logged and skipped
#     rather than silently overwriting an existing ownership row.
#   - Pre-existing rows in whp.user_databases / whp.database_users are LEFT
#     ALONE if the (owner_username, database_name) or (username, host,
#     database_name) tuple matches what we'd write -- the ON DUPLICATE KEY
#     UPDATE re-stamps the row but does not change ownership or privileges
#     that the panel already manages.
#   - Summary files that predate the `mysql_users` key are tolerated: the
#     script processes whp.user_databases rows for that import and skips
#     whp.database_users rows (logging a note per skipped import).
#   - Summary files missing entirely (cleanup sweeper, manual rm) are logged
#     and skipped.
#
# This script is safe to run on a server with NO completed imports (it logs
# "nothing to do" and exits 0).

set -euo pipefail

LOG_DIR=/var/log/whp
SUMMARY_DIR=/var/lib/whp/cpanel-imports
TIMESTAMP=$(date -u +%Y%m%d-%H%M%S)
LOG_FILE="$LOG_DIR/cpanel-import-db-tracking-backfill-$TIMESTAMP.log"

mkdir -p "$LOG_DIR"
chmod 750 "$LOG_DIR"

log() {
    printf '[%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" | tee -a "$LOG_FILE"
}

log "=== cpanel-import db-tracking backfill starting (host: $(hostname)) ==="

# --- preflight: mysql client + connectivity ---------------------------------
if ! command -v mysql >/dev/null 2>&1; then
    log "FATAL: mysql client not on PATH"
    exit 1
fi
if ! command -v php >/dev/null 2>&1; then
    log "FATAL: php not on PATH (needed to parse summary JSON files)"
    exit 1
fi
if [ ! -r /root/.my.cnf ]; then
    log "FATAL: /root/.my.cnf not readable; cannot reach mysql container"
    exit 1
fi
if ! mysql --defaults-file=/root/.my.cnf -Nse 'SELECT 1' >/dev/null 2>&1; then
    log "FATAL: cannot connect to mysql server with /root/.my.cnf"
    exit 1
fi

MYSQL="mysql --defaults-file=/root/.my.cnf -N -B"

# --- preflight: tracking tables exist ---------------------------------------
# Older WHP servers shipped without these tables. complete-database-setup.sql
# has them as of 2026.05+; if they are missing on this host the backfill
# simply has nowhere to write. Fail loud rather than silently no-op so the
# operator sees what's wrong.
for tbl in user_databases database_users cpanel_imports; do
    if ! $MYSQL -e "SELECT 1 FROM whp.${tbl} LIMIT 1" >/dev/null 2>>"$LOG_FILE"; then
        log "FATAL: whp.${tbl} does not exist on this server -- run the canonical schema first"
        exit 1
    fi
done

# --- preflight: summary directory -------------------------------------------
if [ ! -d "$SUMMARY_DIR" ]; then
    log "summary directory $SUMMARY_DIR missing -- no cpanel imports have ever run on this host"
    log "nothing to do"
    log "=== backfill complete (0 imports) ==="
    exit 0
fi

# --- enumerate completed imports --------------------------------------------
# Pull the (import_id, username) pairs we expect to backfill. Status filter
# matches the importer's own definition of "succeeded".
tmpfile_imports=$(mktemp -t whp-backfill-imports.XXXXXX)
$MYSQL -e "SELECT import_id, username FROM whp.cpanel_imports WHERE status = 'completed' ORDER BY id" > "$tmpfile_imports" 2>>"$LOG_FILE" || {
    log "FATAL: could not enumerate whp.cpanel_imports rows"
    rm -f -- "$tmpfile_imports"
    exit 1
}

IMPORT_COUNT=$(wc -l < "$tmpfile_imports" | tr -d ' ')
log "found ${IMPORT_COUNT} completed cpanel import(s) to consider"

if [ "$IMPORT_COUNT" -eq 0 ]; then
    rm -f -- "$tmpfile_imports"
    log "nothing to do"
    log "=== backfill complete (0 imports) ==="
    exit 0
fi

# --- per-import processing --------------------------------------------------
TOTAL_DB_ROWS=0
TOTAL_DBUSER_ROWS=0
SKIPPED_IMPORTS_NO_SUMMARY=0
SKIPPED_IMPORTS_NO_MYSQL_USERS_KEY=0
SKIPPED_DBS_OWNER_MISMATCH=0

# Write the two PHP helpers to tmpfiles once. We use a tmpfile + arg invocation
# (php helper.php summary.json) rather than `php -r` because passing the helper
# inline via -r requires stripping the <?php tag, and the heredoc-as-file form
# is far less error-prone. Both files are removed in the cleanup trap below.
PHP_HELPER_DBS=$(mktemp -t whp-backfill-helper-dbs.XXXXXX.php)
PHP_HELPER_PAIRS=$(mktemp -t whp-backfill-helper-pairs.XXXXXX.php)
trap 'rm -f -- "$PHP_HELPER_DBS" "$PHP_HELPER_PAIRS"' EXIT

# PHP helper #1: prints database_name<TAB>size_bytes<NL>... from a summary file.
# size_bytes is empty string when null/missing (the bash side converts to NULL).
cat > "$PHP_HELPER_DBS" <<'PHP'
<?php
$path = $argv[1] ?? '';
if (!is_file($path)) { fwrite(STDERR, "summary not found: $path\n"); exit(2); }
$raw = file_get_contents($path);
if ($raw === false) { fwrite(STDERR, "summary unreadable: $path\n"); exit(3); }
$j = json_decode($raw, true);
if (!is_array($j)) { fwrite(STDERR, "summary not valid json: $path\n"); exit(4); }
$dbs = $j['databases'] ?? [];
foreach ($dbs as $db) {
    if (!is_array($db)) continue;
    $name = $db['whp_name'] ?? '';
    if ($name === '') continue;
    $size = $db['size_bytes'] ?? null;
    $sizeStr = is_int($size) ? (string)$size : '';
    echo $name . "\t" . $sizeStr . "\n";
}
PHP

# PHP helper #2: prints whp_user<TAB>db_name<NL>... by cross-referencing
# mysql_users[].whp_name against databases[].whp_name. The summary does NOT
# preserve the original cpmove grant pairs -- it only carries the rename maps.
# The safe fallback is to emit the cartesian product of imported users x
# imported DBs for this import_id, which is what replayMysqlGrants effectively
# produced for typical cPanel layouts where one panel user owns one cPanel
# account (the only layout we import). Cross-account drift is impossible here:
# every imported MySQL user and every imported DB inside a single cpanel_imports
# row by definition share the panel username (the import's owner).
# Exits 5 when summary lacks the mysql_users key (predates the importer fix).
cat > "$PHP_HELPER_PAIRS" <<'PHP'
<?php
$path = $argv[1] ?? '';
if (!is_file($path)) { fwrite(STDERR, "summary not found: $path\n"); exit(2); }
$raw = file_get_contents($path);
if ($raw === false) { fwrite(STDERR, "summary unreadable: $path\n"); exit(3); }
$j = json_decode($raw, true);
if (!is_array($j)) { fwrite(STDERR, "summary not valid json: $path\n"); exit(4); }
if (!array_key_exists('mysql_users', $j)) { exit(5); }
$users = $j['mysql_users'] ?? [];
$dbs   = $j['databases']   ?? [];
$userNames = [];
foreach ($users as $u) {
    if (!is_array($u)) continue;
    $n = $u['whp_name'] ?? '';
    if ($n !== '') $userNames[$n] = true;
}
$dbNames = [];
foreach ($dbs as $d) {
    if (!is_array($d)) continue;
    $n = $d['whp_name'] ?? '';
    if ($n !== '') $dbNames[$n] = true;
}
foreach (array_keys($userNames) as $u) {
    foreach (array_keys($dbNames) as $d) {
        echo $u . "\t" . $d . "\n";
    }
}
PHP

while IFS=$'\t' read -r IMPORT_ID OWNER; do
    [ -z "$IMPORT_ID" ] && continue

    summary_path="$SUMMARY_DIR/${IMPORT_ID}.json"
    log "--- import_id=${IMPORT_ID} owner=${OWNER} ---"

    if [ ! -f "$summary_path" ]; then
        log "  SKIP: summary file missing: $summary_path (cron sweeper may have removed it)"
        SKIPPED_IMPORTS_NO_SUMMARY=$((SKIPPED_IMPORTS_NO_SUMMARY + 1))
        continue
    fi

    # --- whp.user_databases rows ---
    dbs_tsv=$(mktemp -t whp-backfill-dbs.XXXXXX)
    if ! php -d display_errors=0 "$PHP_HELPER_DBS" "$summary_path" > "$dbs_tsv" 2>>"$LOG_FILE"; then
        log "  WARN: could not parse summary for db list (exit=$?); leaving import alone"
        rm -f -- "$dbs_tsv"
        continue
    fi

    db_added=0
    while IFS=$'\t' read -r DB_NAME SIZE_BYTES; do
        [ -z "$DB_NAME" ] && continue

        # Owner sanity. Renamed DB names follow `<owner>_<orig>_<hash>`.
        # If the prefix does not match the cpanel_imports row's username,
        # skip rather than potentially writing the wrong owner_username.
        case "$DB_NAME" in
            "${OWNER}_"*)
                ;;
            *)
                log "  SKIP db (owner mismatch): db=${DB_NAME} expected prefix=${OWNER}_"
                SKIPPED_DBS_OWNER_MISMATCH=$((SKIPPED_DBS_OWNER_MISMATCH + 1))
                continue
                ;;
        esac

        # SQL-escape values for the literal INSERT.
        db_esc=${DB_NAME//\'/\'\'}
        owner_esc=${OWNER//\'/\'\'}
        if [ -n "$SIZE_BYTES" ]; then
            size_lit="$SIZE_BYTES"
        else
            size_lit="NULL"
        fi

        insert_sql="INSERT INTO whp.user_databases (database_name, owner_username, size_bytes) VALUES ('${db_esc}', '${owner_esc}', ${size_lit}) ON DUPLICATE KEY UPDATE owner_username = VALUES(owner_username), size_bytes = COALESCE(VALUES(size_bytes), size_bytes);"
        if $MYSQL -e "$insert_sql" 2>>"$LOG_FILE"; then
            db_added=$((db_added + 1))
        else
            log "  WARN: insert into whp.user_databases failed for db=${DB_NAME}"
        fi
    done < "$dbs_tsv"
    rm -f -- "$dbs_tsv"
    log "  whp.user_databases: applied ${db_added} row(s) for import ${IMPORT_ID}"
    TOTAL_DB_ROWS=$((TOTAL_DB_ROWS + db_added))

    # --- whp.database_users rows ---
    pairs_tsv=$(mktemp -t whp-backfill-pairs.XXXXXX)
    set +e
    php -d display_errors=0 "$PHP_HELPER_PAIRS" "$summary_path" > "$pairs_tsv" 2>>"$LOG_FILE"
    rc=$?
    set -e
    if [ "$rc" -eq 5 ]; then
        log "  SKIP whp.database_users: summary predates mysql_users key for import ${IMPORT_ID}"
        SKIPPED_IMPORTS_NO_MYSQL_USERS_KEY=$((SKIPPED_IMPORTS_NO_MYSQL_USERS_KEY + 1))
        rm -f -- "$pairs_tsv"
        continue
    elif [ "$rc" -ne 0 ]; then
        log "  WARN: could not parse summary for user/db pairs (exit=${rc}); leaving import alone"
        rm -f -- "$pairs_tsv"
        continue
    fi

    dbuser_added=0
    while IFS=$'\t' read -r WHP_USER WHP_DB; do
        [ -z "$WHP_USER" ] && continue
        [ -z "$WHP_DB" ] && continue

        case "$WHP_USER" in
            "${OWNER}_"*)
                ;;
            *)
                log "  SKIP dbuser (owner mismatch): user=${WHP_USER} expected prefix=${OWNER}_"
                continue
                ;;
        esac
        case "$WHP_DB" in
            "${OWNER}_"*)
                ;;
            *)
                log "  SKIP dbuser (db owner mismatch): user=${WHP_USER} db=${WHP_DB}"
                continue
                ;;
        esac

        user_esc=${WHP_USER//\'/\'\'}
        db_esc=${WHP_DB//\'/\'\'}
        owner_esc=${OWNER//\'/\'\'}

        insert_sql="INSERT INTO whp.database_users (username, host, database_name, privileges, created_by) VALUES ('${user_esc}', '%', '${db_esc}', 'ALL', '${owner_esc}') ON DUPLICATE KEY UPDATE privileges = VALUES(privileges), created_by = VALUES(created_by);"
        if $MYSQL -e "$insert_sql" 2>>"$LOG_FILE"; then
            dbuser_added=$((dbuser_added + 1))
        else
            log "  WARN: insert into whp.database_users failed for user=${WHP_USER} db=${WHP_DB}"
        fi
    done < "$pairs_tsv"
    rm -f -- "$pairs_tsv"
    log "  whp.database_users: applied ${dbuser_added} row(s) for import ${IMPORT_ID}"
    TOTAL_DBUSER_ROWS=$((TOTAL_DBUSER_ROWS + dbuser_added))

done < "$tmpfile_imports"

rm -f -- "$tmpfile_imports"

# --- finalize ---------------------------------------------------------------
log "=== backfill complete ==="
log "  imports considered:                           ${IMPORT_COUNT}"
log "  imports skipped (summary file missing):       ${SKIPPED_IMPORTS_NO_SUMMARY}"
log "  imports skipped (summary lacks mysql_users):  ${SKIPPED_IMPORTS_NO_MYSQL_USERS_KEY}"
log "  whp.user_databases rows applied:              ${TOTAL_DB_ROWS}"
log "  whp.database_users rows applied:              ${TOTAL_DBUSER_ROWS}"
log "  databases skipped (owner prefix mismatch):    ${SKIPPED_DBS_OWNER_MISMATCH}"
log "  paper trail: $LOG_FILE"

exit 0
