#!/usr/bin/env bash
# =============================================================================
# onx-directadmin-import — Full DirectAdmin (admin_backup_user.sh) import driver
#
# DirectAdmin user-level yedeğini (<user>.tar.zst / .tar.gz) uçtan uca ayrıştırır
# ve Laravel'in ingest edeceği TİPLİ JSON manifest'leri içeren bir staging dizini
# üretir. ŞEMA cPanel ile AYNI (emails/forwarders/databases/dns_zones/ssl_certs/
# cron_jobs/domains.json) → CpanelImporter::ingest* metodları DEĞİŞMEDEN tüketir.
# Tek fark: ayrıştırma DirectAdmin layout'una göredir.
#
# DA arşiv yapısı (admin_backup_user.sh):
#   backup/user.conf                 -- hesap meta (name=, domain=, email=, ip=)
#   backup/<domain>/domain.conf      -- per-domain (domain=, ssl=ON, active=yes)
#   backup/<domain>/<domain>.db      -- BIND zone (DNS)
#   backup/<domain>/subdomain.list   -- subdomain'ler (her satır bir alt-alan)
#   backup/<domain>/email/passwd     -- e-posta hesapları (local:$6$hash)
#   backup/<domain>/email/quota      -- kota (local:bytes)
#   backup/<domain>/email/aliases    -- forwarder (source: dest1,dest2)
#   backup/<domain>/ftp.passwd       -- FTP (user=passwd=$6$..&path=..&type=..)
#   backup/<domain>/domain.cert/.key/.cacert  -- SSL (ayrı PEM dosyaları)
#   backup/<db>.sql + <db>.conf      -- MySQL dump + (dbuser=..&passwd=*HASH&..priv=Y)
#   backup/crontab.conf              -- cron
#   domains/<domain>/public_html     -- web içeriği
#   imap/<domain>/<local>/Maildir    -- mail içeriği
#
# Input/Output: onx-cpanel-import ile AYNI sözleşme (stdin JSON, stdout JSON).
# Exit codes: 0 ok / 1 invalid input / 2 preflight / 3 exec
# Deployed to: /usr/local/onoxsoft/bin/onx-directadmin-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"

require_root
require_cmd tar
require_cmd jq
# DA yedekleri zstd-sıkıştırmalı → zstd ŞART. (Kurulum bağımlılığı: dnf install zstd)
require_cmd zstd

onx_json_input

# ─── Parse input (cPanel ile aynı) ───────────────────────────────────────────
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
if [[ "$MODE" != "inspect" ]]; then
    [[ -z "$TARGET_USER" ]] && onx_die 1 "target_username zorunlu (mode=$MODE)"
    onx_validate_username "$TARGET_USER"
fi

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/*|/opt/*/storage/app/*|/tmp/*) : ;;
    *) 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"

_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 0751 "$STAGING_ROOT"
chmod o+x "$(dirname "$STAGING_ROOT")" 2>/dev/null || true
# Eski staging orphan'larini temizle (yer kaplamasin): tamamlanmis/iptal gocten kalan
# da-*/cp-*/cpmove-* dizinleri 6 saatten eskiyse sil. extracted/ icerigi buyuk sitelerde
# GB'larca olabilir; orphan birikimini her yeni goc baslangicinda supururuz (self-prune,
# ek cron/sysapi gerektirmez). 6 saat esigi: hicbir aktif gocu yarida silmez.
find "$STAGING_ROOT" -mindepth 1 -maxdepth 1 -type d \( -name 'da-*' -o -name 'cp-*' -o -name 'cpmove-*' \) -mmin +360 -exec rm -rf {} + 2>/dev/null || true
STAGING="$(mktemp -d -p "$STAGING_ROOT" da-XXXXXX)"
chmod 0750 "$STAGING"

trap 'onx_rollback_run' ERR
onx_rollback_register "rm -rf '$STAGING'"

# ─── Extract archive (.tar.zst veya .tar.gz) ─────────────────────────────────
EXTRACT_DIR="$STAGING/extracted"
mkdir -p "$EXTRACT_DIR"
onx_log "da-import: extracting $ARCHIVE_REAL → $EXTRACT_DIR"
case "$ARCHIVE_REAL" in
    *.tar.zst|*.tzst)
        # GNU tar --zstd zstd binary'sine shell-out eder (require_cmd zstd garantiledi).
        tar --zstd -xf "$ARCHIVE_REAL" -C "$EXTRACT_DIR" \
            || zstd -dc "$ARCHIVE_REAL" | tar -xf - -C "$EXTRACT_DIR" \
            || onx_die 3 "tar --zstd extract başarısız: $ARCHIVE_REAL" ;;
    *.tar.gz|*.tgz)
        tar -xzf "$ARCHIVE_REAL" -C "$EXTRACT_DIR" \
            || onx_die 3 "tar extract başarısız: $ARCHIVE_REAL" ;;
    *)
        tar -xf "$ARCHIVE_REAL" -C "$EXTRACT_DIR" \
            || onx_die 3 "tar extract başarısız: $ARCHIVE_REAL" ;;
esac

# DA arşivi DÜZ açılır: <extract>/backup/ + domains/ + imap/  (cpmove gibi sarmalayıcı yok).
# Bazı varyantlar tek üst dizine sarabilir → backup/ neredeyse onu kök al.
if [[ -d "$EXTRACT_DIR/backup" ]]; then
    SOURCE_ROOT="$EXTRACT_DIR"
else
    SOURCE_ROOT="$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 2 -type d -name backup 2>/dev/null | head -n1)"
    SOURCE_ROOT="$(dirname "${SOURCE_ROOT:-$EXTRACT_DIR}")"
fi
[[ -d "$SOURCE_ROOT/backup" ]] || onx_die 3 "DA backup/ dizini bulunamadı (geçerli admin_backup_user yedeği mi?)"

# ─── Parse meta (backup/user.conf) ───────────────────────────────────────────
USER_CONF="$SOURCE_ROOT/backup/user.conf"
SOURCE_USER=""
PRIMARY_DOMAIN=""
DA_VERSION="directadmin"
if [[ -f "$USER_CONF" ]]; then
    SOURCE_USER="$(grep -E '^name=' "$USER_CONF" 2>/dev/null | head -n1 | cut -d= -f2- | tr -d '\r' || true)"
    PRIMARY_DOMAIN="$(grep -E '^domain=' "$USER_CONF" 2>/dev/null | head -n1 | cut -d= -f2- | tr -d '\r' || true)"
fi
[[ -z "$SOURCE_USER" ]] && SOURCE_USER="$(basename "$ARCHIVE_REAL" | sed -E 's/\.(tar\.(zst|gz)|tzst|tgz)$//')"
PRIMARY_DOMAIN="$(printf '%s' "$PRIMARY_DOMAIN" | tr 'A-Z' 'a-z')"

# ─── Helpers — JSON manifests (cPanel ile AYNI dosya adları/şema) ────────────
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"
FTP_JSON="$STAGING/ftp.json"
DOMAINS_JSON="$STAGING/domains.json"

EMAIL_COUNT=0; FORWARDER_COUNT=0; DB_COUNT=0; DNS_COUNT=0
SSL_COUNT=0; FTP_COUNT=0; CRON_COUNT=0; DOMAIN_COUNT=0; HOMEDIR_BYTES=0

# DA domain config dizinleri: backup/<domain>/ içinde domain.conf olanlar.
# (backup/ altında user.conf, crontab.conf, *.sql, *.conf, email_data, history... de var.)
_da_domains() {
    local d
    for d in "$SOURCE_ROOT"/backup/*/; do
        [[ -f "${d}domain.conf" ]] || continue
        basename "$d"
    done
}

# Hedef home çöz (getent; yoksa fallback). cPanel importer ile aynı mantık.
_target_home() {
    local h
    h="$(getent passwd "$TARGET_USER" 2>/dev/null | cut -d: -f6 || true)"
    [[ -z "$h" ]] && h="/home/users/$TARGET_USER"
    printf '%s' "$h"
}

# ─── 1) Home rsync (execute) — DA domains/<d>/public_html → hedef home ───────
# DA layout: /home/<user>/domains/<domain>/public_html. Bizim panel: primary
# docroot = <home>/public_html, addon = <home>/<addon>. Buna göre remap.
if [[ -d "$SOURCE_ROOT/domains" ]]; then
    # || true: du okunamayan path'te non-zero dönüp pipefail+set -e ile TÜM scripti öldürmesin.
    HOMEDIR_BYTES="$(du -sb "$SOURCE_ROOT/domains" 2>/dev/null | awk '{print $1}' || true)"
    [[ -z "$HOMEDIR_BYTES" ]] && HOMEDIR_BYTES=0
fi
if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip homedir; then
    TARGET_HOME="$(_target_home)"
    mkdir -p "$TARGET_HOME"
    _rsync_dir() { # $1 src  $2 dst
        [[ -d "$1" ]] || return 0
        mkdir -p "$2"
        if command -v rsync >/dev/null 2>&1; then
            rsync -a --no-owner --no-group "$1"/ "$2"/ 2>/dev/null || onx_log "da-import: rsync WARN $1 → $2"
        else
            cp -a "$1"/. "$2"/ 2>/dev/null || onx_log "da-import: cp WARN $1 → $2"
        fi
    }
    if [[ -d "$SOURCE_ROOT/domains" ]]; then
        for ddir in "$SOURCE_ROOT"/domains/*/; do
            [[ -d "$ddir" ]] || continue
            dname="$(basename "$ddir")"
            if [[ "$dname" == "$PRIMARY_DOMAIN" ]]; then
                _rsync_dir "${ddir}public_html" "$TARGET_HOME/public_html"
            else
                _rsync_dir "${ddir}public_html" "$TARGET_HOME/$dname"
            fi
        done
        id -u "$TARGET_USER" >/dev/null 2>&1 && chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME" 2>/dev/null || true
        chmod 0750 "$TARGET_HOME/public_html" 2>/dev/null || true
    fi

    # Mail içeriği: imap/<domain>/<local>/Maildir → /var/vmail/<domain>/<local>/Maildir
    if [[ -d "$SOURCE_ROOT/imap" ]] && ! _is_skip email; then
        for mdom in "$SOURCE_ROOT"/imap/*/; do
            [[ -d "$mdom" ]] || continue
            mdomain="$(basename "$mdom")"
            [[ "$mdomain" =~ ^[a-zA-Z0-9.-]+$ ]] || continue   # güvenli domain (path-traversal)
            for mloc in "$mdom"*/; do
                [[ -d "${mloc}Maildir" ]] || continue
                local_part="$(basename "$mloc")"
                [[ "$local_part" =~ ^[a-zA-Z0-9._-]+$ ]] || continue
                vdst="/var/vmail/$mdomain/$local_part/Maildir"
                mkdir -p "$vdst"
                if command -v rsync >/dev/null 2>&1; then
                    rsync -a --no-owner --no-group \
                        --exclude='dovecot.index*' --exclude='dovecot-uidlist*' \
                        "${mloc}Maildir"/ "$vdst"/ 2>/dev/null || onx_log "da-import: mail rsync WARN $mdomain/$local_part"
                fi
            done
        done
        if id -u vmail >/dev/null 2>&1; then
            chown -R vmail:vmail "/var/vmail" 2>/dev/null || true
        fi
        chmod -R u+rwX,g+rwX,o-rwx "/var/vmail" 2>/dev/null || true
    fi
fi

# ─── 2) Domains (addon/subdomain) → domains.json ─────────────────────────────
emit_domains() {
    local thome; thome="$(_target_home)"
    local first=1
    {
        printf '['
        # Addon = primary HARİÇ domain config dizinleri.
        local dom
        while IFS= read -r dom; do
            [[ -z "$dom" ]] && continue
            dom="$(printf '%s' "$dom" | tr 'A-Z' 'a-z')"
            [[ "$dom" == "$PRIMARY_DOMAIN" ]] && continue
            [[ "$dom" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] || continue
            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc --arg name "$dom" --arg type "addon" \
                --arg doc_root "$thome/$dom" --arg parent "" \
                '{name:$name, type:$type, doc_root:$doc_root, parent:$parent}'
            DOMAIN_COUNT=$((DOMAIN_COUNT + 1))
        done < <(_da_domains)

        # Subdomain'ler: her domain'in subdomain.list dosyası (her satır: <sub>).
        # DA sub docroot: domains/<domain>/public_html/<sub> (genelde). parent = ait olduğu domain.
        local sdir sub parentdom
        for sdir in "$SOURCE_ROOT"/backup/*/subdomain.list; do
            [[ -f "$sdir" ]] || continue
            parentdom="$(basename "$(dirname "$sdir")" | tr 'A-Z' 'a-z')"
            while IFS= read -r sub; do
                sub="$(printf '%s' "$sub" | tr -d '\r' | tr 'A-Z' 'a-z')"
                [[ -z "$sub" ]] && continue
                local fqdn="${sub}.${parentdom}"
                [[ "$fqdn" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] || continue
                [[ $first -eq 0 ]] && printf ','
                first=0
                local sdocroot
                if [[ "$parentdom" == "$PRIMARY_DOMAIN" ]]; then
                    sdocroot="$thome/public_html/$sub"
                else
                    sdocroot="$thome/$parentdom/$sub"
                fi
                jq -nc --arg name "$fqdn" --arg type "subdomain" \
                    --arg doc_root "$sdocroot" --arg parent "$parentdom" \
                    '{name:$name, type:$type, doc_root:$doc_root, parent:$parent}'
                DOMAIN_COUNT=$((DOMAIN_COUNT + 1))
            done < "$sdir"
        done
        printf ']'
    } > "$DOMAINS_JSON"
}
if ! _is_skip domains; then emit_domains; else echo "[]" > "$DOMAINS_JSON"; fi

# ─── 3) Databases → databases.json (+ execute restore) ───────────────────────
emit_databases() {
    local first=1
    {
        printf '['
        local sql_file original size_bytes
        for sql_file in "$SOURCE_ROOT"/backup/*.sql; do
            [[ -f "$sql_file" ]] || continue
            original="$(basename "$sql_file" .sql)"
            [[ "$original" =~ ^[A-Za-z0-9_]+$ ]] || continue   # güvenli db adı (path-traversal/parite)
            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 "$original" \
                --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
    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
            [[ "$new_name" =~ ^[a-zA-Z0-9_]+$ ]] || { onx_log "da-import: SKIP unsafe db '$new_name'"; continue; }
            mysql_exec_root "" "CREATE DATABASE IF NOT EXISTS \`${new_name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" \
                || onx_die 3 "mysql create DB failed: $new_name"
            mysql_exec_root_stdin "$new_name" < "$sql_file" \
                || onx_die 3 "mysql import failed: $new_name"
            onx_log "da-import: imported db=$new_name"
        done < <(jq -c '.[]' "$DATABASES_JSON")
    fi
fi

# ─── 3b) MySQL kullanıcıları + yetkileri (<db>.conf) ─────────────────────────
# DA <db>.conf satırları:  <dbuser>=...&passwd=*HASH&<priv>_priv=Y&...
# (db_collation, accesshosts satırları kullanıcı DEĞİL → atla.) Her .conf'taki
# kullanıcılar O db'ye grant alır. Hash *HEX40 (mysql_native) → IDENTIFIED BY PASSWORD.
restore_db_users() {
    command -v mysql >/dev/null 2>&1 || return 0
    local conf db dbuser hash line sqlfile
    sqlfile="$(mktemp)" || return 0
    for conf in "$SOURCE_ROOT"/backup/*.conf; do
        [[ -f "$conf" ]] || continue
        db="$(basename "$conf" .conf)"
        [[ "$db" =~ ^[A-Za-z0-9_]+$ ]] || continue
        # user.conf / domain.conf / ticket.conf gibi DB-OLMAYAN .conf'ları ele:
        # sadece <db>.sql'i de olan .conf'ları DB kabul et.
        [[ -f "$SOURCE_ROOT/backup/$db.sql" ]] || continue
        while IFS= read -r line; do
            [[ "$line" =~ ^(db_collation|accesshosts)= ]] && continue
            [[ "$line" == *"passwd=*"* ]] || continue
            dbuser="${line%%=*}"
            [[ "$dbuser" =~ ^[A-Za-z0-9_]+$ ]] || continue
            # passwd=*HASH değerini ayıkla (& ile sınırlı)
            hash="$(printf '%s' "$line" | grep -oE 'passwd=\*[A-Fa-f0-9]{40}' | head -n1 | cut -d= -f2)"
            [[ "$hash" =~ ^\*[A-Fa-f0-9]{40}$ ]] || continue
            printf "CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY PASSWORD '%s';\n" "$dbuser" "$hash" >> "$sqlfile"
            printf "GRANT ALL PRIVILEGES ON \`%s\`.* TO '%s'@'localhost';\n" "$db" "$dbuser" >> "$sqlfile"
        done < "$conf"
    done
    echo "FLUSH PRIVILEGES;" >> "$sqlfile"
    local stmts; stmts="$(grep -c ';' "$sqlfile" 2>/dev/null || echo 0)"
    if [[ "${stmts:-0}" -gt 1 ]]; then
        mysql_exec_root_stdin "" < "$sqlfile" \
            && onx_log "da-import: db-users restored (${stmts} stmt)" \
            || onx_log "da-import: db-user restore WARN (devam)"
    fi
    rm -f "$sqlfile"
}
if [[ "$MODE" == "execute" ]] && [[ "$DRY_RUN" != "true" ]] && ! _is_skip mysql; then
    restore_db_users || onx_log "da-import: restore_db_users WARN (devam)"
fi

# ─── 4) Email accounts → emails.json (backup/<domain>/email/passwd) ──────────
emit_emails() {
    local first=1
    {
        printf '['
        # NOT: 'local' bir bash builtin → değişken adı olarak KULLANMA (local local = hata). lpart kullan.
        local edir domain pwfile qfile lpart hash quota qline
        for edir in "$SOURCE_ROOT"/backup/*/email; do
            [[ -d "$edir" ]] || continue
            domain="$(basename "$(dirname "$edir")" | tr 'A-Z' 'a-z')"
            pwfile="$edir/passwd"
            [[ -f "$pwfile" ]] || continue
            qfile="$edir/quota"
            while IFS=: read -r lpart hash; do
                [[ -z "$lpart" || -z "$hash" ]] && continue
                [[ "$lpart" =~ ^[A-Za-z0-9._-]+$ ]] || continue
                # Dovecot scheme prefix ($6$ = SHA512-CRYPT)
                [[ "$hash" == \$* ]] && hash="{SHA512-CRYPT}${hash}"
                # Default: SINIRSIZ (-1; panel/dovecot konvansiyonu — onx-mailbox-quota -1=unlimited).
                # DA mailbox'ları çoğunlukla sınırsız; göçte 1GB cap büyük kutuda mail reddi = 0 kayıp ihlali.
                # DA quota dosyası: 'local:bytes' / 'local@domain:bytes' / '...=bytes'; 0 = sınırsız.
                quota=-1
                if [[ -f "$qfile" ]]; then
                    qline="$(grep -E "^(${lpart}|${lpart}@${domain})[:=]" "$qfile" 2>/dev/null | head -n1 | awk -F'[:=]' '{print $2}' | tr -dc '0-9')"
                    if [[ -n "$qline" && "$qline" -gt 0 ]]; then
                        if [[ "$qline" -gt 1048576 ]]; then quota=$((qline/1024/1024)); else quota="$qline"; fi
                    fi
                fi
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc --arg local "$lpart" --arg domain "$domain" \
                    --arg hash "$hash" --argjson quota "$quota" \
                    '{local:$local, domain:$domain, password_hash:$hash, quota_mb:$quota}'
                EMAIL_COUNT=$((EMAIL_COUNT + 1))
            done < "$pwfile"
        done
        printf ']'
    } > "$EMAILS_JSON"
}
if ! _is_skip email; then emit_emails; else echo "[]" > "$EMAILS_JSON"; fi

# ─── 5) Forwarders → forwarders.json (backup/<domain>/email/aliases) ─────────
# DA aliases:  source: dest1,dest2   (source local part; self-alias = mailbox, atla)
emit_forwarders() {
    local first=1
    {
        printf '['
        local adir domain src dests
        for adir in "$SOURCE_ROOT"/backup/*/email/aliases; do
            [[ -f "$adir" ]] || continue
            domain="$(basename "$(dirname "$(dirname "$adir")")" | tr 'A-Z' 'a-z')"
            while IFS= read -r line; do
                line="$(printf '%s' "$line" | tr -d '\r')"
                [[ "$line" == *:* ]] || continue
                src="$(printf '%s' "${line%%:*}" | xargs)"
                dests="$(printf '%s' "${line#*:}" | xargs)"
                [[ -z "$src" || -z "$dests" ]] && continue
                # self-alias (source == dest, mailbox'un kendisi) → forwarder değil, atla
                [[ "$src" == "$dests" ]] && continue
                [[ "$src" =~ ^[A-Za-z0-9._-]+$ ]] || continue
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc --arg source "${src}@${domain}" --arg destinations "$dests" \
                    '{source:$source, destinations:$destinations}'
                FORWARDER_COUNT=$((FORWARDER_COUNT + 1))
            done < "$adir"
        done
        printf ']'
    } > "$FORWARDERS_JSON"
}
if ! _is_skip email; then emit_forwarders; else echo "[]" > "$FORWARDERS_JSON"; fi

# ─── 6) DNS zones → dns_zones.json (backup/<domain>/<domain>.db = BIND) ──────
emit_dns_zones() {
    local first=1
    {
        printf '['
        local zfile domain records rcount
        for zfile in "$SOURCE_ROOT"/backup/*/*.db; do
            [[ -f "$zfile" ]] || continue
            domain="$(basename "$zfile" .db | tr 'A-Z' 'a-z')"
            [[ "$domain" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] || continue
            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")"
            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

# ─── 7) SSL → ssl_certs.json (domain.cert + .key + .cacert ayrı PEM) ─────────
emit_ssl_certs() {
    local first=1
    {
        printf '['
        local ddir domain cfile kfile chfile cert key chain expires
        for ddir in "$SOURCE_ROOT"/backup/*/; do
            domain="$(basename "$ddir" | tr 'A-Z' 'a-z')"
            [[ "$domain" =~ ^[a-z0-9.-]+\.[a-z]{2,}$ ]] || continue
            cfile="${ddir}domain.cert"; kfile="${ddir}domain.key"; chfile="${ddir}domain.cacert"
            [[ -s "$cfile" && -s "$kfile" ]] || continue
            cert="$(cat "$cfile" 2>/dev/null)"; key="$(cat "$kfile" 2>/dev/null)"
            chain=""; [[ -s "$chfile" ]] && chain="$(cat "$chfile" 2>/dev/null)"
            [[ "$cert" == *"BEGIN CERTIFICATE"* && "$key" == *"PRIVATE KEY"* ]] || continue
            expires="$(printf '%s' "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || true)"
            [[ -n "$expires" ]] && expires="$(date -d "$expires" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || true)"
            [[ $first -eq 0 ]] && printf ','
            first=0
            jq -nc --arg domain "$domain" --arg certificate "$cert" \
                --arg private_key "$key" --arg chain "$chain" --arg expires_at "${expires:-}" \
                '{domain:$domain, certificate:$certificate, private_key:$private_key, chain:$chain, expires_at:$expires_at}'
            SSL_COUNT=$((SSL_COUNT + 1))
        done
        printf ']'
    } > "$SSL_CERTS_JSON"
}
if ! _is_skip ssl; then emit_ssl_certs; else echo "[]" > "$SSL_CERTS_JSON"; fi

# ─── 8) FTP → ftp.json (backup/<domain>/ftp.passwd) ──────────────────────────
# DA:  <ftpuser>=passwd=$6$..&path=<path>&type=<type>
# Atla: type=system (ana sistem FTP) + boş passwd. logical = local part (FtpProvisioner regex'e uydur).
emit_ftp() {
    local first=1
    {
        printf '['
        local ffile domain line ftpuser kv path type logical reldir thome
        thome="$(_target_home)"
        for ffile in "$SOURCE_ROOT"/backup/*/ftp.passwd; do
            [[ -f "$ffile" ]] || continue
            domain="$(basename "$(dirname "$ffile")" | tr 'A-Z' 'a-z')"
            while IFS= read -r line; do
                line="$(printf '%s' "$line" | tr -d '\r')"
                [[ "$line" == *=passwd=* ]] || continue
                ftpuser="${line%%=*}"
                kv="${line#*=}"
                type="$(printf '%s' "$kv" | grep -oE 'type=[^&]*' | head -n1 | cut -d= -f2)"
                path="$(printf '%s' "$kv" | grep -oE 'path=[^&]*' | head -n1 | cut -d= -f2)"
                # Ana sistem FTP (kullanıcının kendisi) → atla
                [[ "$type" == "system" ]] && continue
                [[ "$ftpuser" == "$SOURCE_USER" ]] && continue
                # passwd boşsa atla (DA bazı ftp@ kayıtlarını boş bırakır)
                printf '%s' "$kv" | grep -qE 'passwd=\$[0-9a-zA-Z]' || continue
                logical="${ftpuser%@*}"
                logical="$(printf '%s' "$logical" | tr 'A-Z' 'a-z' | tr -cd 'a-z0-9_' | cut -c1-31)"
                [[ "$logical" =~ ^[a-z] ]] || logical="ftp${logical}"
                [[ ${#logical} -ge 3 ]] || logical="${logical}ftp"
                logical="$(printf '%s' "$logical" | cut -c1-31)"
                [[ -n "$logical" ]] || continue
                # relative_dir: path home altındaysa göreli, değilse boş (PHP home köküne koyar)
                reldir=""
                if [[ -n "$path" && "$path" == /home/"$SOURCE_USER"/* ]]; then
                    reldir="${path#/home/$SOURCE_USER/}"
                fi
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc --arg ftp_user "$ftpuser" --arg logical "$logical" \
                    --arg home_dir "$path" --arg relative_dir "$reldir" \
                    '{ftp_user:$ftp_user, logical:$logical, home_dir:$home_dir, relative_dir:$relative_dir}'
                FTP_COUNT=$((FTP_COUNT + 1))
            done < "$ffile"
        done
        printf ']'
    } > "$FTP_JSON"
}
if ! _is_skip ftp; then emit_ftp; else echo "[]" > "$FTP_JSON"; fi

# ─── 9) Cron → cron_jobs.json (backup/crontab.conf) ──────────────────────────
emit_cron() {
    local first=1
    {
        printf '['
        local cfile="$SOURCE_ROOT/backup/crontab.conf"
        if [[ -f "$cfile" ]]; then
            local line mins expr command
            while IFS= read -r line; do
                line="$(printf '%s' "$line" | tr -d '\r')"
                # Yorum / ortam değişkeni (PATH=, MAILTO=, SHELL=) atla
                [[ "$line" =~ ^[[:space:]]*# ]] && continue
                [[ "$line" =~ ^[[:space:]]*[A-Za-z_]+= ]] && continue
                [[ -z "${line// }" ]] && continue
                # 5 alan schedule + komut
                expr="$(printf '%s' "$line" | awk '{print $1, $2, $3, $4, $5}')"
                command="$(printf '%s' "$line" | awk '{$1=$2=$3=$4=$5=""; sub(/^ +/,""); print}')"
                [[ -z "$command" ]] && continue
                [[ $first -eq 0 ]] && printf ','
                first=0
                jq -nc --arg expression "$expr" --arg command "$command" \
                    '{expression:$expression, command:$command}'
                CRON_COUNT=$((CRON_COUNT + 1))
            done < "$cfile"
        fi
        printf ']'
    } > "$CRON_JOBS_JSON"
}
if ! _is_skip cron; then emit_cron; else echo "[]" > "$CRON_JOBS_JSON"; fi

# ─── Audit + manifest izinleri (non-root panel okuyabilsin) ──────────────────
onx_audit "onx-directadmin-import" "mode=$MODE source=$SOURCE_USER target=$TARGET_USER stagedir=$STAGING"
trap - ERR

if [[ "$MODE" == "execute" ]]; then
    REF_DIR="$(dirname "$ARCHIVE_REAL")"
    chown --reference="$REF_DIR" "$STAGING" 2>/dev/null || true
    for _mf in "$EMAILS_JSON" "$FORWARDERS_JSON" "$DATABASES_JSON" \
               "$DNS_ZONES_JSON" "$SSL_CERTS_JSON" "$CRON_JOBS_JSON" \
               "$FTP_JSON" "$DOMAINS_JSON"; do
        [[ -f "$_mf" ]] || continue
        chown --reference="$REF_DIR" "$_mf" 2>/dev/null || true
        chmod 0640 "$_mf" 2>/dev/null || true
    done
fi

# ─── Output (cPanel ile AYNI şema) ───────────────────────────────────────────
jq -n \
    --arg src "$SOURCE_USER" --arg dst "$TARGET_USER" --arg pkg "$DA_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" \
    --argjson ftps "$FTP_COUNT" --argjson doms "$DOMAIN_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" \
    --arg ftp_f "$FTP_JSON" --arg doms_f "$DOMAINS_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,
            ftp: $ftps, domains: $doms },
        manifests: { emails: $emails_f, forwarders: $fwds_f, databases: $dbs_f,
            dns_zones: $dns_f, ssl_certs: $ssl_f, cron_jobs: $cron_f, ftp: $ftp_f, domains: $doms_f }
    }'
