#!/usr/bin/env bash
# =============================================================================
# onx-cpanel-import — Full cPanel pkgacct import driver
#
# Parses a cpmove-<USER>.tar.gz archive end-to-end and emits a staging dir
# containing typed JSON manifests for Laravel to ingest. By design the script
# never writes to the panel database; it only writes to disk locations the
# rest of the sysapi stack already owns (/home/<user>, /var/lib/mysql via
# `mysql`, /etc/onoxsoft/imported-ssl, /etc/cron.d).
#
# Input (stdin JSON):
#   {
#     "archive_path":    "/home/onx_acme01/migrations/cpmove-acme.tar.gz",  -- required
#     "target_username": "onx_acme01",                                       -- required
#     "mode":            "inspect|plan|execute",                             -- required
#     "skip":            ["homedir","mysql","dns","email","ssl","cron"],     -- optional
#     "dry_run":         false                                                -- optional
#   }
#
# Output (stdout JSON, one object — see footer for full shape):
#   {
#     "source_user":     "acme",
#     "target_user":     "onx_acme01",
#     "pkgacct_version": "11.110",
#     "primary_domain":  "acme.com",
#     "staging_dir":     "/var/lib/onox/migrations/cpanel-XXXX",
#     "mode":            "execute",
#     "dry_run":         false,
#     "skip":            [],
#     "imported": {
#       "homedir_bytes":  483920104,
#       "emails":         12,
#       "forwarders":     3,
#       "databases":      4,
#       "dns_zones":      2,
#       "ssl_certs":      1,
#       "cron_jobs":      5
#     },
#     "manifests": {
#       "emails":      ".../emails.json",
#       "forwarders":  ".../forwarders.json",
#       "databases":   ".../databases.json",
#       "dns_zones":   ".../dns_zones.json",
#       "ssl_certs":   ".../ssl_certs.json",
#       "cron_jobs":   ".../cron_jobs.json"
#     }
#   }
#
# Exit codes: 0 ok / 1 invalid input / 2 preflight / 3 exec
#
# Deployed to: /usr/local/onoxsoft/bin/onx-cpanel-import
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

readonly STAGING_ROOT="/var/lib/onox/migrations"
readonly SSL_IMPORT_ROOT="/etc/onoxsoft/imported-ssl"

require_root
require_cmd tar
require_cmd jq

onx_json_input

# ─── Parse input ─────────────────────────────────────────────────────────────
ARCHIVE_PATH="$(onx_json_field archive_path)"
TARGET_USER="$(onx_json_field target_username)"
MODE="$(onx_json_field mode 'inspect')"
DRY_RUN="$(onx_json_get_bool "$INPUT" dry_run 'false')"
SKIP_JSON="$(printf '%s' "$INPUT" | jq -c '.skip // []')"

[[ -z "$ARCHIVE_PATH" ]] && onx_die 1 "archive_path zorunlu"
[[ -z "$MODE" ]] && MODE="inspect"

case "$MODE" in
    inspect|plan|execute) : ;;
    *) onx_die 1 "mode geçersiz (inspect|plan|execute bekleniyor): $MODE" ;;
esac

# Target username only required for plan / execute
if [[ "$MODE" != "inspect" ]]; then
    [[ -z "$TARGET_USER" ]] && onx_die 1 "target_username zorunlu (mode=$MODE)"
    onx_validate_username "$TARGET_USER"
fi

# Resolve & sandbox archive path
ARCHIVE_REAL="$(realpath -e "$ARCHIVE_PATH" 2>/dev/null)" \
    || onx_die 1 "Arşiv bulunamadı: $ARCHIVE_PATH"

case "$ARCHIVE_REAL" in
    /home/*|/var/lib/onox/*|/var/www/*/storage/app/*) : ;;
    *) onx_die 1 "archive_path güvenli alanda değil: $ARCHIVE_REAL" ;;
esac

[[ -f "$ARCHIVE_REAL" ]] || onx_die 1 "archive_path bir dosya değil: $ARCHIVE_REAL"

# Helper — is_skip section
_is_skip() {
    local section="$1"
    echo "$SKIP_JSON" | jq -e --arg s "$section" 'index($s) != null' >/dev/null 2>&1
}

# ─── Stage directory ─────────────────────────────────────────────────────────
mkdir -p "$STAGING_ROOT"
chmod 0750 "$STAGING_ROOT"

STAGING="$(mktemp -d -p "$STAGING_ROOT" cpanel-XXXXXX)"
chmod 0750 "$STAGING"

# Rollback registers staging cleanup if anything fails
trap 'onx_rollback_run' ERR
onx_rollback_register "rm -rf '$STAGING'"

# ─── Extract archive ─────────────────────────────────────────────────────────
EXTRACT_DIR="$STAGING/extracted"
mkdir -p "$EXTRACT_DIR"

onx_log "cpanel-import: extracting $ARCHIVE_REAL → $EXTRACT_DIR"
tar -xzf "$ARCHIVE_REAL" -C "$EXTRACT_DIR" \
    || onx_die 3 "tar extract başarısız: $ARCHIVE_REAL"

# Locate top-level cpmove-<user>/ directory (or use first dir)
SOURCE_ROOT="$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)"
[[ -n "$SOURCE_ROOT" && -d "$SOURCE_ROOT" ]] \
    || onx_die 3 "Beklenen üst dizin bulunamadı (cpmove-<user>/)"

BASENAME="$(basename "$SOURCE_ROOT")"
# cpmove-<user> → <user>;  bazı yaşlı pkgacct'lar direkt <user>/ kullanır.
if [[ "$BASENAME" =~ ^cpmove-(.+)$ ]]; then
    SOURCE_USER="${BASH_REMATCH[1]}"
else
    SOURCE_USER="$BASENAME"
fi

# ─── Parse meta ──────────────────────────────────────────────────────────────
PKGACCT_VERSION="unknown"
PRIMARY_DOMAIN=""

if [[ -f "$SOURCE_ROOT/meta/pkgacctversion" ]]; then
    PKGACCT_VERSION="$(head -n1 "$SOURCE_ROOT/meta/pkgacctversion" 2>/dev/null | tr -d '\r\n' || echo unknown)"
fi
if [[ -f "$SOURCE_ROOT/version" ]]; then
    PKGACCT_VERSION="$(head -n1 "$SOURCE_ROOT/version" 2>/dev/null | tr -d '\r\n' || echo "$PKGACCT_VERSION")"
fi

# Primary domain from cp/<user> file (key=value)
CP_FILE="$SOURCE_ROOT/cp/$SOURCE_USER"
if [[ -f "$CP_FILE" ]]; then
    PRIMARY_DOMAIN="$(grep -E '^DNS(=|:)' "$CP_FILE" 2>/dev/null | head -n1 | sed -E 's/^DNS[=:][[:space:]]*//' | tr -d '\r' || true)"
    [[ -z "$PRIMARY_DOMAIN" ]] && \
        PRIMARY_DOMAIN="$(grep -E '^DOMAIN(=|:)' "$CP_FILE" 2>/dev/null | head -n1 | sed -E 's/^DOMAIN[=:][[:space:]]*//' | tr -d '\r' || true)"
fi

# ─── Helpers — JSON manifests ────────────────────────────────────────────────
EMAILS_JSON="$STAGING/emails.json"
FORWARDERS_JSON="$STAGING/forwarders.json"
DATABASES_JSON="$STAGING/databases.json"
DNS_ZONES_JSON="$STAGING/dns_zones.json"
SSL_CERTS_JSON="$STAGING/ssl_certs.json"
CRON_JOBS_JSON="$STAGING/cron_jobs.json"

EMAIL_COUNT=0
FORWARDER_COUNT=0
DB_COUNT=0
DNS_COUNT=0
SSL_COUNT=0
CRON_COUNT=0
HOMEDIR_BYTES=0

# ─── 1) Homedir size + (execute) rsync ───────────────────────────────────────
if [[ -d "$SOURCE_ROOT/homedir" ]]; then
    HOMEDIR_BYTES="$(du -sb "$SOURCE_ROOT/homedir" 2>/dev/null | awk '{print $1}')"
    [[ -z "$HOMEDIR_BYTES" ]] && HOMEDIR_BYTES=0
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip homedir; then
    if [[ -d "$SOURCE_ROOT/homedir" ]]; then
        TARGET_HOME="/home/$TARGET_USER"
        mkdir -p "$TARGET_HOME"
        onx_log "cpanel-import: rsync homedir → $TARGET_HOME"

        if command -v rsync >/dev/null 2>&1; then
            rsync -a --no-owner --no-group "$SOURCE_ROOT/homedir/" "$TARGET_HOME/" \
                || onx_die 3 "rsync homedir başarısız"
        else
            cp -a "$SOURCE_ROOT/homedir/." "$TARGET_HOME/" \
                || onx_die 3 "cp homedir başarısız"
        fi

        # Ownership reset (target user must already exist via onx-user-add)
        if id -u "$TARGET_USER" >/dev/null 2>&1; then
            chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME" 2>/dev/null || true
        fi
        chmod 0750 "$TARGET_HOME/public_html" 2>/dev/null || true
    fi
fi

# ─── 2) Email accounts → emails.json ─────────────────────────────────────────
# cPanel stores email passwd as: local:domain:password_hash:quota:...
# password_hash is $6$salt$hash (SHA512-CRYPT) — Dovecot-compatible directly.
emit_emails() {
    local etc_dir="$SOURCE_ROOT/etc/$SOURCE_USER"
    [[ -d "$etc_dir" ]] || etc_dir="$SOURCE_ROOT/etc"
    [[ -d "$etc_dir" ]] || { echo "[]" > "$EMAILS_JSON"; return; }

    local first=1
    {
        printf '['
        # /etc/<user>/<domain>/{passwd,quota,shadow}
        for d_dir in "$etc_dir"/*/; do
            [[ -d "$d_dir" ]] || continue
            local domain
            domain="$(basename "$d_dir")"
            local pfile="${d_dir}passwd"
            local qfile="${d_dir}quota"
            local sfile="${d_dir}shadow"
            [[ -f "$pfile" ]] || continue

            # Quota lookup map
            declare -A QMAP
            QMAP=()
            if [[ -f "$qfile" ]]; then
                while IFS=: read -r q_local q_mb _ ; do
                    [[ -z "$q_local" ]] && continue
                    QMAP["$q_local"]="${q_mb:-1024}"
                done < "$qfile"
            fi

            # Shadow lookup (preferred hash source)
            declare -A SMAP
            SMAP=()
            if [[ -f "$sfile" ]]; then
                while IFS=: read -r s_local s_hash _ ; do
                    [[ -z "$s_local" ]] && continue
                    SMAP["$s_local"]="$s_hash"
                done < "$sfile"
            fi

            while IFS=: read -r local p_domain p_hash p_quota _; do
                [[ -z "$local" ]] && continue
                # Prefer shadow hash if present
                local hash="${SMAP[$local]:-$p_hash}"
                [[ -z "$hash" || "$hash" == "*" || "$hash" == "!" ]] && hash=""
                local quota="${QMAP[$local]:-${p_quota:-1024}}"
                [[ "$quota" =~ ^[0-9]+$ ]] || quota=1024

                # Dovecot SHA512-CRYPT scheme prefix if missing
                if [[ -n "$hash" && "${hash:0:1}" == '$' ]]; then
                    hash="{SHA512-CRYPT}${hash}"
                fi

                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg local "$local" \
                    --arg domain "$domain" \
                    --arg hash "$hash" \
                    --argjson quota_mb "$quota" \
                    '{local:$local, domain:$domain, password_hash:$hash, quota_mb:$quota_mb}'
                EMAIL_COUNT=$((EMAIL_COUNT + 1))
            done < "$pfile"
        done
        printf ']'
    } > "$EMAILS_JSON"
}

if ! _is_skip email; then
    emit_emails
else
    echo "[]" > "$EMAILS_JSON"
fi

# ─── 3) Forwarders → forwarders.json ─────────────────────────────────────────
# cPanel aliases:  source: dest1,dest2
emit_forwarders() {
    local first=1
    {
        printf '['
        for vali in "$SOURCE_ROOT/etc/valiases"/* "$SOURCE_ROOT/etc/$SOURCE_USER/valiases"/*; do
            [[ -f "$vali" ]] || continue
            local domain
            domain="$(basename "$vali")"
            while IFS= read -r line; do
                # Skip comments / blanks / *: defaults
                [[ -z "$line" || "${line:0:1}" == '#' ]] && continue
                [[ "$line" == \**: ]] && continue
                # Split "source: dest"
                local src dst
                src="${line%%:*}"
                dst="${line#*:}"
                src="$(echo "$src" | xargs)"
                dst="$(echo "$dst" | xargs)"
                [[ -z "$src" || -z "$dst" ]] && continue
                # source can be either "user" (→ user@domain) or full email
                if [[ "$src" != *@* ]]; then
                    src="${src}@${domain}"
                fi
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg src "$src" \
                    --arg dst "$dst" \
                    --arg domain "$domain" \
                    '{source:$src, destinations:$dst, domain:$domain}'
                FORWARDER_COUNT=$((FORWARDER_COUNT + 1))
            done < "$vali"
        done
        printf ']'
    } > "$FORWARDERS_JSON"
}

if ! _is_skip email; then
    emit_forwarders
else
    echo "[]" > "$FORWARDERS_JSON"
fi

# ─── 4) MySQL databases → databases.json (+ execute restores) ────────────────
emit_databases() {
    local first=1
    {
        printf '['
        local mysql_dir="$SOURCE_ROOT/mysql"
        [[ -d "$mysql_dir" ]] || { printf ']'; return; }

        for sql_file in "$mysql_dir"/*.sql; do
            [[ -f "$sql_file" ]] || continue
            local original
            original="$(basename "$sql_file" .sql)"

            # Skip the inventory file itself (some pkgacct emit <user>.sql wrapper)
            [[ "$original" == "$SOURCE_USER" ]] && continue

            # cPanel db naming: <cpaneluser>_<name>. Renaming to onoxsoft target:
            local new_name="$original"
            if [[ "$original" == "${SOURCE_USER}_"* ]]; then
                local suffix="${original#${SOURCE_USER}_}"
                new_name="${TARGET_USER}_${suffix}"
            elif [[ -n "$TARGET_USER" ]]; then
                new_name="${TARGET_USER}_${original}"
            fi

            local size_bytes
            size_bytes="$(stat -c%s "$sql_file" 2>/dev/null || echo 0)"

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg orig "$original" \
                --arg new "$new_name" \
                --arg sql "$sql_file" \
                --argjson size "$size_bytes" \
                '{original_name:$orig, new_name:$new, sql_file:$sql, size_bytes:$size}'
            DB_COUNT=$((DB_COUNT + 1))
        done
        printf ']'
    } > "$DATABASES_JSON"
}

if ! _is_skip mysql; then
    emit_databases
else
    echo "[]" > "$DATABASES_JSON"
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip mysql; then
    # Iterate the just-emitted manifest and restore each DB.
    if command -v mysql >/dev/null 2>&1; then
        while IFS= read -r row; do
            new_name="$(echo "$row" | jq -r '.new_name')"
            sql_file="$(echo "$row" | jq -r '.sql_file')"
            [[ -z "$new_name" || -z "$sql_file" ]] && continue
            # Defensive — only allow ^[a-zA-Z0-9_]+$ names
            [[ "$new_name" =~ ^[a-zA-Z0-9_]+$ ]] || {
                onx_log "cpanel-import: SKIP unsafe db name '$new_name'"
                continue
            }
            mysql -e "CREATE DATABASE IF NOT EXISTS \`${new_name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \
                || onx_die 3 "mysql create DB failed: $new_name"
            mysql "$new_name" < "$sql_file" \
                || onx_die 3 "mysql import failed: $new_name"
            onx_log "cpanel-import: imported db=$new_name"
        done < <(jq -c '.[]' "$DATABASES_JSON")
    else
        onx_log "cpanel-import: mysql client not found — skipping DB restore"
    fi
fi

# ─── 5) DNS zones → dns_zones.json (records parsed) ──────────────────────────
emit_dns_zones() {
    local first=1
    {
        printf '['
        local dz_dir="$SOURCE_ROOT/dnszones"
        [[ -d "$dz_dir" ]] || { printf ']'; return; }
        for zfile in "$dz_dir"/*.db "$dz_dir"/*.zone "$dz_dir"/*; do
            [[ -f "$zfile" ]] || continue
            # Only accept plausible zone names
            local fname
            fname="$(basename "$zfile")"
            local domain="${fname%.db}"
            domain="${domain%.zone}"

            # Records → JSON array via awk (mirrors onx-bind-zone-parse)
            local records
            records="$(awk -v dom="$domain" '
                BEGIN { ttl_default = 14400; first = 1; printf "[" }
                /^;/ || /^$/ { next }
                /^\$TTL/ { gsub(/[^0-9]/, "", $2); ttl_default = $2; next }
                /^\$/ { next }
                { gsub(/[ \t]+;.*$/, "") }
                /^[A-Za-z0-9@_\.*-]/ {
                    name = $1
                    idx = 2
                    ttl = ttl_default
                    if ($idx ~ /^[0-9]+$/) { ttl = $idx; idx++ }
                    if (toupper($idx) == "IN") idx++
                    type = toupper($idx); idx++
                    if (type == "SOA" || type == "RRSIG" || type == "NSEC" || type == "DNSKEY" || type == "DS") next
                    rdata = ""
                    for (i = idx; i <= NF; i++) { if (i > idx) rdata = rdata " "; rdata = rdata $i }
                    prio = 0
                    if (type == "MX" || type == "SRV") {
                        split(rdata, parts, " ")
                        if (parts[1] ~ /^[0-9]+$/) { prio = parts[1]; rdata = parts[2]; for (i=3; i<=length(parts); i++) rdata = rdata " " parts[i] }
                    }
                    gsub(/\\/, "\\\\", rdata); gsub(/"/, "\\\"", rdata)
                    gsub(/\\/, "\\\\", name); gsub(/"/, "\\\"", name)
                    if (name == "@") name = dom
                    if (!first) printf ","
                    first = 0
                    printf "{\"name\":\"%s\",\"type\":\"%s\",\"ttl\":%d,\"prio\":%d,\"content\":\"%s\"}", name, type, ttl, prio, rdata
                }
                END { print "]" }
            ' "$zfile")"

            local rcount
            rcount="$(echo "$records" | jq 'length' 2>/dev/null || echo 0)"

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg domain "$domain" \
                --argjson records "$records" \
                --argjson rcount "$rcount" \
                '{domain:$domain, record_count:$rcount, records:$records}'
            DNS_COUNT=$((DNS_COUNT + 1))
        done
        printf ']'
    } > "$DNS_ZONES_JSON"
}

if ! _is_skip dns; then
    emit_dns_zones
else
    echo "[]" > "$DNS_ZONES_JSON"
fi

# ─── 6) SSL certs → ssl_certs.json (+ execute copies) ────────────────────────
emit_ssl_certs() {
    local first=1
    {
        printf '['
        local ssl_root="$SOURCE_ROOT/ssl"
        [[ -d "$ssl_root" ]] || { printf ']'; return; }

        # cPanel stores certs in ssl/certs/ and keys in ssl/keys/; older variants
        # use ssl/cert and ssl/key (singular). Handle both.
        for cert_dir in "$ssl_root/certs" "$ssl_root/cert"; do
            [[ -d "$cert_dir" ]] || continue
            for crt in "$cert_dir"/*.crt "$cert_dir"/*; do
                [[ -f "$crt" ]] || continue
                # Skip anything that doesn't look like PEM
                head -n1 "$crt" 2>/dev/null | grep -q 'BEGIN CERTIFICATE' || continue

                local cn
                cn="$(basename "$crt")"
                cn="${cn%.crt}"

                local expires=""
                if command -v openssl >/dev/null 2>&1; then
                    expires="$(openssl x509 -in "$crt" -noout -enddate 2>/dev/null | sed 's/notAfter=//' || true)"
                fi

                # Locate matching key
                local key=""
                for kd in "$ssl_root/keys" "$ssl_root/key"; do
                    [[ -d "$kd" ]] || continue
                    if [[ -f "$kd/${cn}.key" ]]; then
                        key="$kd/${cn}.key"
                        break
                    fi
                done

                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc \
                    --arg domain "$cn" \
                    --arg cert_path "$crt" \
                    --arg key_path "$key" \
                    --arg expires "$expires" \
                    '{domain:$domain, cert_path:$cert_path, key_path:$key_path, expires_at:$expires}'
                SSL_COUNT=$((SSL_COUNT + 1))
            done
        done
        printf ']'
    } > "$SSL_CERTS_JSON"
}

if ! _is_skip ssl; then
    emit_ssl_certs
else
    echo "[]" > "$SSL_CERTS_JSON"
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip ssl; then
    SSL_TARGET="$SSL_IMPORT_ROOT/$TARGET_USER"
    mkdir -p "$SSL_TARGET"
    chmod 0700 "$SSL_TARGET"
    while IFS= read -r row; do
        crt="$(echo "$row" | jq -r '.cert_path')"
        key="$(echo "$row" | jq -r '.key_path')"
        domain="$(echo "$row" | jq -r '.domain')"
        [[ -f "$crt" ]] && cp -f "$crt" "$SSL_TARGET/${domain}.crt"
        [[ -f "$key" ]] && cp -f "$key" "$SSL_TARGET/${domain}.key"
        chmod 0600 "$SSL_TARGET/${domain}.crt" 2>/dev/null || true
        chmod 0600 "$SSL_TARGET/${domain}.key" 2>/dev/null || true
    done < <(jq -c '.[]' "$SSL_CERTS_JSON")
    onx_log "cpanel-import: SSL certs copied → $SSL_TARGET"
fi

# ─── 7) Cron jobs → cron_jobs.json (+ execute write) ─────────────────────────
emit_cron() {
    local first=1
    {
        printf '['
        local cron_file="$SOURCE_ROOT/cron/$SOURCE_USER"
        [[ -f "$cron_file" ]] || { printf ']'; return; }
        while IFS= read -r line; do
            line="${line%%$'\r'}"
            # Strip blanks and comments and MAILTO/PATH/SHELL env lines
            [[ -z "$line" ]] && continue
            [[ "${line:0:1}" == '#' ]] && continue
            [[ "$line" == MAILTO=* || "$line" == PATH=* || "$line" == SHELL=* ]] && continue

            # Parse cron expression — first 5 fields = schedule, rest = command
            local m h d mo w
            local rest
            read -r m h d mo w rest <<<"$line"
            [[ -z "$rest" ]] && continue

            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc \
                --arg m "$m" --arg h "$h" --arg d "$d" \
                --arg mo "$mo" --arg w "$w" --arg cmd "$rest" \
                '{minute:$m, hour:$h, day:$d, month:$mo, weekday:$w, command:$cmd, expression:($m+" "+$h+" "+$d+" "+$mo+" "+$w)}'
            CRON_COUNT=$((CRON_COUNT + 1))
        done < "$cron_file"
        printf ']'
    } > "$CRON_JOBS_JSON"
}

if ! _is_skip cron; then
    emit_cron
else
    echo "[]" > "$CRON_JOBS_JSON"
fi

if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip cron; then
    CRON_DST="/etc/cron.d/onoxsoft-$TARGET_USER"
    if [[ -s "$CRON_JOBS_JSON" ]] && [[ "$(jq 'length' "$CRON_JOBS_JSON" 2>/dev/null || echo 0)" -gt 0 ]]; then
        {
            printf '# Onoxsoft cPanel import (%s → %s)\n' "$SOURCE_USER" "$TARGET_USER"
            printf 'SHELL=/bin/bash\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\nMAILTO=""\n\n'
            jq -r --arg user "$TARGET_USER" \
                '.[] | "\(.minute) \(.hour) \(.day) \(.month) \(.weekday) \($user) \(.command)"' \
                "$CRON_JOBS_JSON"
        } > "$CRON_DST.tmp"
        chmod 0644 "$CRON_DST.tmp"
        chown root:root "$CRON_DST.tmp" 2>/dev/null || true
        mv -f "$CRON_DST.tmp" "$CRON_DST"
        onx_log "cpanel-import: cron written → $CRON_DST"
    fi
fi

# ─── Audit + output ──────────────────────────────────────────────────────────
onx_audit "onx-cpanel-import" "mode=$MODE source=$SOURCE_USER target=$TARGET_USER stagedir=$STAGING"

# On execute success we keep the staging dir for Laravel ingest; the caller is
# responsible for cleanup after committing to the DB.
trap - ERR

# Build the final response
jq -n \
    --arg src "$SOURCE_USER" \
    --arg dst "$TARGET_USER" \
    --arg pkg "$PKGACCT_VERSION" \
    --arg pdom "$PRIMARY_DOMAIN" \
    --arg stagedir "$STAGING" \
    --arg mode "$MODE" \
    --argjson dry "$DRY_RUN" \
    --argjson skip "$SKIP_JSON" \
    --argjson home_bytes "$HOMEDIR_BYTES" \
    --argjson emails "$EMAIL_COUNT" \
    --argjson fwds "$FORWARDER_COUNT" \
    --argjson dbs "$DB_COUNT" \
    --argjson dns "$DNS_COUNT" \
    --argjson ssls "$SSL_COUNT" \
    --argjson crons "$CRON_COUNT" \
    --arg emails_f "$EMAILS_JSON" \
    --arg fwds_f "$FORWARDERS_JSON" \
    --arg dbs_f "$DATABASES_JSON" \
    --arg dns_f "$DNS_ZONES_JSON" \
    --arg ssl_f "$SSL_CERTS_JSON" \
    --arg cron_f "$CRON_JOBS_JSON" \
    '{
        source_user: $src,
        target_user: $dst,
        pkgacct_version: $pkg,
        primary_domain: $pdom,
        staging_dir: $stagedir,
        mode: $mode,
        dry_run: $dry,
        skip: $skip,
        imported: {
            homedir_bytes: $home_bytes,
            emails: $emails,
            forwarders: $fwds,
            databases: $dbs,
            dns_zones: $dns,
            ssl_certs: $ssls,
            cron_jobs: $crons
        },
        manifests: {
            emails:     $emails_f,
            forwarders: $fwds_f,
            databases:  $dbs_f,
            dns_zones:  $dns_f,
            ssl_certs:  $ssl_f,
            cron_jobs:  $cron_f
        }
    }'
