#!/usr/bin/env bash
# =============================================================================
# onx-backup-dest-prune — Remote retention pruner for offsite backup destinations
#
# Mevcut `onx-backup-prune` SADECE local /var/backups/onoxsoft altındaki tarball'lara
# bakar — uzak S3/SFTP/B2 hedeflerine dokunmaz. Bu script "her destination'ın kendi
# retention_days policy'sini uzakta uygula" — eski remote dosyaları siler.
#
# rclone TERCİH EDİLEN tool (tüm sağlayıcılar için tek arayüz). aws-cli (S3/B2)
# ve ssh+find (SFTP) fallback'leri de var.
#
# Input (stdin JSON):
#   {
#     "type":           "s3|wasabi|backblaze_b2|sftp|ftp|local",
#     "retention_days": 30,
#     "config":         { ... },               # Same shape as backup-destination-test
#     "dry_run":        false                  # Optional — list candidates, don't delete
#   }
#
# Output (stdout JSON):
#   {
#     "pruned":          5,
#     "freed_bytes":     N,
#     "kept":            10,
#     "dry_run":         false,
#     "tool":            "rclone|aws|ssh|lftp|fs",
#     "duration_seconds": 12
#   }
#
# Exit codes:
#   0 — ok
#   1 — invalid input
#   2 — preflight (rclone/aws/ssh/lftp not available)
#   3 — execution fail (auth/network/io)
#
# Deployed to: /usr/local/onoxsoft/bin/onx-backup-dest-prune
#
# rclone install (sysadmin notu):
#   AlmaLinux 9:   dnf install -y rclone
#   Manual:        curl https://rclone.org/install.sh | sudo bash
# =============================================================================

set -euo pipefail

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

require_root
onx_json_input

TYPE="$(onx_json_field type '')"
RETENTION_DAYS="$(onx_json_field retention_days '30')"
DRY_RUN_RAW="$(onx_json_get_bool "$INPUT" dry_run 'false')"
CONFIG="$(echo "$INPUT" | jq -c '.config // {}')"

[[ -z "$TYPE" ]] && onx_die 1 "type alanı zorunlu"
[[ "$RETENTION_DAYS" =~ ^[0-9]+$ ]] || onx_die 1 "retention_days non-negative integer olmalı"
[[ "$RETENTION_DAYS" -lt 1 ]] && onx_die 1 "retention_days en az 1 olmalı (0 = tüm dosyalar silinir, güvenlik için yasak)"

START_S=$(date +%s)
PRUNED=0
KEPT=0
FREED=0
TOOL="unknown"

onx_log "backup-dest-prune: type=${TYPE} retention=${RETENTION_DAYS}d dry_run=${DRY_RUN_RAW}"

# Helper — convert "true"/"false" string to rclone --dry-run flag
_dry_flag() {
    [[ "$DRY_RUN_RAW" == "true" ]] && echo "--dry-run" || echo ""
}

# Helper — pretty bytes for log
_bytes_pretty() {
    local b="${1:-0}"
    if [[ $b -lt 1024 ]]; then
        echo "${b} B"
    elif [[ $b -lt 1048576 ]]; then
        echo "$(( b / 1024 )) KB"
    elif [[ $b -lt 1073741824 ]]; then
        echo "$(( b / 1048576 )) MB"
    else
        echo "$(awk -v b="$b" 'BEGIN{printf "%.2f GB", b/1073741824}')"
    fi
}

# ─── Dispatch by destination type ────────────────────────────────────────────
case "$TYPE" in

# ─── Local ────────────────────────────────────────────────────────────────────
local)
    TOOL="fs"
    LOCAL_PATH="$(onx_json_get "$CONFIG" path '')"
    [[ -z "$LOCAL_PATH" ]] && onx_die 1 "config.path zorunlu"
    [[ "$LOCAL_PATH" =~ ^(/var/backups/onox|/mnt/backup) ]] || \
        onx_die 1 "Güvensiz path: ${LOCAL_PATH}"
    [[ -d "$LOCAL_PATH" ]] || onx_die 2 "Dizin bulunamadı: ${LOCAL_PATH}"

    # find by mtime; only files matching *.tar.gz / *.tar.zst pattern
    CUTOFF_MIN=$(( RETENTION_DAYS * 1440 ))  # find -mmin requires minutes

    while IFS= read -r -d '' f; do
        SIZE=$(stat -c '%s' "$f" 2>/dev/null || echo 0)
        if [[ "$DRY_RUN_RAW" == "false" ]]; then
            rm -f "$f" "${f}.sha256" 2>/dev/null || {
                onx_log "WARNING local prune fail: $f"
                continue
            }
        fi
        PRUNED=$(( PRUNED + 1 ))
        FREED=$(( FREED + SIZE ))
        onx_log "pruned local: $f ($(_bytes_pretty $SIZE))"
    done < <(find "$LOCAL_PATH" -maxdepth 4 -type f \
        \( -name '*.tar.gz' -o -name '*.tar.zst' -o -name '*.tar.xz' \) \
        -mmin "+${CUTOFF_MIN}" -print0 2>/dev/null)

    # Count remaining (kept) tarballs
    KEPT=$(find "$LOCAL_PATH" -maxdepth 4 -type f \
        \( -name '*.tar.gz' -o -name '*.tar.zst' -o -name '*.tar.xz' \) 2>/dev/null | wc -l)
    ;;

# ─── S3 / Wasabi / Backblaze B2 (S3-compatible) ──────────────────────────────
s3|wasabi|backblaze_b2)
    BUCKET="$(onx_json_get "$CONFIG" bucket '')"
    [[ -z "$BUCKET" ]] && onx_die 1 "config.bucket zorunlu"

    # B2 might pass application_key_id / application_key (S3-compat); normalize.
    ACCESS_KEY="$(onx_json_get "$CONFIG" access_key '')"
    [[ -z "$ACCESS_KEY" ]] && ACCESS_KEY="$(onx_json_get "$CONFIG" application_key_id '')"
    SECRET_KEY="$(onx_json_get "$CONFIG" secret_key '')"
    [[ -z "$SECRET_KEY" ]] && SECRET_KEY="$(onx_json_get "$CONFIG" application_key '')"
    REGION="$(onx_json_get "$CONFIG" region 'us-east-1')"
    ENDPOINT="$(onx_json_get "$CONFIG" endpoint '')"
    PREFIX="$(onx_json_get "$CONFIG" prefix '')"

    [[ -z "$ACCESS_KEY" ]] && onx_die 1 "config.access_key zorunlu"
    [[ -z "$SECRET_KEY" ]] && onx_die 1 "config.secret_key zorunlu"

    # B2 default endpoint
    if [[ "$TYPE" == "backblaze_b2" && -z "$ENDPOINT" ]]; then
        ENDPOINT="https://s3.${REGION}.backblazeb2.com"
    fi

    # ── rclone preferred (one tool for all providers) ────────────────────────
    if command -v rclone >/dev/null 2>&1; then
        TOOL="rclone"
        # Build ad-hoc remote config via env vars (no persistent rclone.conf needed)
        export RCLONE_CONFIG_DESTREMOTE_TYPE="s3"
        export RCLONE_CONFIG_DESTREMOTE_PROVIDER="${TYPE^^}"
        export RCLONE_CONFIG_DESTREMOTE_ACCESS_KEY_ID="$ACCESS_KEY"
        export RCLONE_CONFIG_DESTREMOTE_SECRET_ACCESS_KEY="$SECRET_KEY"
        export RCLONE_CONFIG_DESTREMOTE_REGION="$REGION"
        [[ -n "$ENDPOINT" ]] && export RCLONE_CONFIG_DESTREMOTE_ENDPOINT="$ENDPOINT"

        REMOTE_PATH="destremote:${BUCKET}"
        [[ -n "$PREFIX" ]] && REMOTE_PATH="${REMOTE_PATH}/${PREFIX}"

        # Use `rclone delete --min-age Xd` — removes files older than retention.
        # `--dry-run` is honoured; stderr captured for diagnostics.
        DRY_FLAG="$(_dry_flag)"
        OUT_FILE=$(mktemp /tmp/onx-rclone-prune-XXXXXX)
        trap 'rm -f "$OUT_FILE"' EXIT

        rclone delete \
            --min-age "${RETENTION_DAYS}d" \
            --include '*.tar.gz' --include '*.tar.zst' --include '*.tar.xz' \
            --include '*.sha256' \
            $DRY_FLAG \
            --stats=0 \
            "$REMOTE_PATH" \
            >"$OUT_FILE" 2>&1 \
            || onx_die 3 "rclone prune başarısız: $(tail -c 500 "$OUT_FILE")"

        # rclone --dry-run lists "Would delete" lines; non-dry mode emits "Deleted".
        # grep -c 0-eşleşmede "0" yazıp exit 1 döner → eski "|| echo 0" 2. bir "0"
        # ekleyip $PRUNED'i "0\n0" yapıyordu → jq --argjson "invalid JSON" patlatıyordu.
        # Fallback'i substitution DIŞINA al: 0-eşleşme/hata → PRUNED=0 (tek tamsayı).
        PRUNED=$(grep -cE '(Deleted|Would delete)' "$OUT_FILE" 2>/dev/null) || PRUNED=0
        # Best-effort size — rclone doesn't always report bytes per file in delete output
        FREED=0

        # Count remaining files
        KEPT=$(rclone size "$REMOTE_PATH" --json 2>/dev/null | jq -r '.count // 0')

    # ── aws-cli fallback (only S3-compat — works for B2 too) ─────────────────
    elif command -v aws >/dev/null 2>&1; then
        TOOL="aws"
        ENDPOINT_ARG=""
        [[ -n "$ENDPOINT" ]] && ENDPOINT_ARG="--endpoint-url=${ENDPOINT}"

        # Cutoff in seconds since epoch
        CUTOFF_EPOCH=$(date -d "${RETENTION_DAYS} days ago" +%s 2>/dev/null || \
                       date -v "-${RETENTION_DAYS}d" +%s)

        S3_URI="s3://${BUCKET}"
        [[ -n "$PREFIX" ]] && S3_URI="${S3_URI}/${PREFIX}"

        # List objects → filter by LastModified < cutoff → delete each
        AWS_ACCESS_KEY_ID="$ACCESS_KEY" \
        AWS_SECRET_ACCESS_KEY="$SECRET_KEY" \
        AWS_DEFAULT_REGION="$REGION" \
        aws s3api list-objects-v2 \
            --bucket "$BUCKET" \
            ${PREFIX:+--prefix "$PREFIX"} \
            $ENDPOINT_ARG \
            --output json 2>/dev/null \
            | jq -r --argjson cutoff "$CUTOFF_EPOCH" '
                .Contents // []
                | map(select(
                    (.LastModified | fromdateiso8601) < $cutoff
                    and (.Key | test("\\.(tar\\.gz|tar\\.zst|tar\\.xz|sha256)$"))
                ))
                | .[] | "\(.Size) \(.Key)"
              ' \
            | while read -r line; do
                SIZE="${line%% *}"
                KEY="${line#* }"
                if [[ "$DRY_RUN_RAW" == "false" ]]; then
                    AWS_ACCESS_KEY_ID="$ACCESS_KEY" \
                    AWS_SECRET_ACCESS_KEY="$SECRET_KEY" \
                    AWS_DEFAULT_REGION="$REGION" \
                    aws s3api delete-object \
                        --bucket "$BUCKET" --key "$KEY" \
                        $ENDPOINT_ARG >/dev/null 2>&1 \
                        || { onx_log "WARNING delete fail: $KEY"; continue; }
                fi
                PRUNED=$(( PRUNED + 1 ))
                FREED=$(( FREED + SIZE ))
                onx_log "pruned remote: ${BUCKET}/${KEY} ($(_bytes_pretty $SIZE))"
              done

        # Kept count
        KEPT=$(AWS_ACCESS_KEY_ID="$ACCESS_KEY" \
               AWS_SECRET_ACCESS_KEY="$SECRET_KEY" \
               AWS_DEFAULT_REGION="$REGION" \
               aws s3api list-objects-v2 \
                   --bucket "$BUCKET" \
                   ${PREFIX:+--prefix "$PREFIX"} \
                   $ENDPOINT_ARG --output json 2>/dev/null \
               | jq '.Contents // [] | length' 2>/dev/null || echo 0)
    else
        onx_die 2 "rclone veya aws-cli kurulu değil. Önerilen: 'dnf install -y rclone' (AlmaLinux 9), 'curl https://rclone.org/install.sh | sudo bash' (manuel kurulum). Tüm sağlayıcılar için tek tool — config.env vars üzerinden persistent rclone.conf gerekmez."
    fi
    ;;

# ─── SFTP ─────────────────────────────────────────────────────────────────────
sftp)
    HOST="$(onx_json_get "$CONFIG" host '')"
    PORT="$(onx_json_get "$CONFIG" port '22')"
    USER="$(onx_json_get "$CONFIG" username '')"
    PRIV_KEY="$(onx_json_get "$CONFIG" private_key '')"
    PASSWORD="$(onx_json_get "$CONFIG" password '')"
    REMOTE_PATH="$(onx_json_get "$CONFIG" remote_path '/backup')"

    [[ -z "$HOST" ]] && onx_die 1 "config.host zorunlu"
    [[ -z "$USER" ]] && onx_die 1 "config.username zorunlu"

    SSH_OPTS=(-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o BatchMode=yes -p "$PORT")
    TMP_KEY=""

    if [[ -n "$PRIV_KEY" ]]; then
        TMP_KEY=$(mktemp /tmp/onx-sftp-key-XXXXXX)
        chmod 600 "$TMP_KEY"
        printf '%s\n' "$PRIV_KEY" > "$TMP_KEY"
        trap 'rm -f "$TMP_KEY"' EXIT
        SSH_OPTS+=(-i "$TMP_KEY")
        SSH_CMD=(ssh "${SSH_OPTS[@]}")
    elif [[ -n "$PASSWORD" ]]; then
        command -v sshpass >/dev/null 2>&1 || onx_die 2 "sshpass kurulu değil (password auth için gerekli)"
        SSH_CMD=(sshpass -p "$PASSWORD" ssh "${SSH_OPTS[@]}")
    else
        onx_die 1 "config.private_key veya config.password gerekli"
    fi

    TOOL="ssh"

    # Remote find — list old files newline-separated as "SIZE PATH"
    REMOTE_CMD="find '${REMOTE_PATH}' -maxdepth 4 -type f \\( -name '*.tar.gz' -o -name '*.tar.zst' -o -name '*.tar.xz' -o -name '*.sha256' \\) -mtime +${RETENTION_DAYS} -printf '%s %p\\n' 2>/dev/null || true"

    OUT_FILE=$(mktemp /tmp/onx-sftp-prune-XXXXXX)
    trap 'rm -f "$OUT_FILE" "$TMP_KEY"' EXIT

    "${SSH_CMD[@]}" "${USER}@${HOST}" "$REMOTE_CMD" >"$OUT_FILE" 2>/dev/null \
        || onx_die 3 "SFTP bağlantısı veya remote find başarısız: ${USER}@${HOST}:${PORT}"

    while IFS=' ' read -r SIZE FPATH; do
        [[ -z "$FPATH" ]] && continue
        if [[ "$DRY_RUN_RAW" == "false" ]]; then
            "${SSH_CMD[@]}" "${USER}@${HOST}" "rm -f '${FPATH}'" 2>/dev/null \
                || { onx_log "WARNING SFTP delete fail: $FPATH"; continue; }
        fi
        PRUNED=$(( PRUNED + 1 ))
        FREED=$(( FREED + SIZE ))
        onx_log "pruned sftp: ${USER}@${HOST}:${FPATH} ($(_bytes_pretty $SIZE))"
    done < "$OUT_FILE"

    # Kept count
    KEPT_CMD="find '${REMOTE_PATH}' -maxdepth 4 -type f \\( -name '*.tar.gz' -o -name '*.tar.zst' -o -name '*.tar.xz' \\) 2>/dev/null | wc -l"
    KEPT=$("${SSH_CMD[@]}" "${USER}@${HOST}" "$KEPT_CMD" 2>/dev/null || echo 0)
    ;;

# ─── FTP ──────────────────────────────────────────────────────────────────────
ftp)
    command -v lftp >/dev/null 2>&1 || onx_die 2 "lftp kurulu değil"
    TOOL="lftp"

    HOST="$(onx_json_get "$CONFIG" host '')"
    PORT="$(onx_json_get "$CONFIG" port '21')"
    USER="$(onx_json_get "$CONFIG" username '')"
    PASS="$(onx_json_get "$CONFIG" password '')"
    REMOTE_PATH="$(onx_json_get "$CONFIG" remote_path '/backup')"
    PASSIVE="$(onx_json_get "$CONFIG" passive 'true')"

    [[ -z "$HOST" ]] && onx_die 1 "config.host zorunlu"
    [[ -z "$USER" ]] && onx_die 1 "config.username zorunlu"
    [[ -z "$PASS" ]] && onx_die 1 "config.password zorunlu"

    PASSIVE_SETTING="set ftp:passive-mode yes"
    [[ "$PASSIVE" == "false" ]] && PASSIVE_SETTING="set ftp:passive-mode no"

    # lftp doesn't have a direct "delete older than" — we list, then loop-delete
    OUT_FILE=$(mktemp /tmp/onx-ftp-prune-XXXXXX)
    trap 'rm -f "$OUT_FILE"' EXIT

    # `cls -l` gives mtime; parse with awk; CUTOFF in epoch.
    CUTOFF_EPOCH=$(date -d "${RETENTION_DAYS} days ago" +%s 2>/dev/null || \
                   date -v "-${RETENTION_DAYS}d" +%s)

    lftp -u "${USER},${PASS}" "ftp://${HOST}:${PORT}" <<EOF >"$OUT_FILE" 2>&1
$PASSIVE_SETTING
set ftp:ssl-allow yes
cd ${REMOTE_PATH}
cls -l --time-style=+%s
bye
EOF

    while read -r line; do
        # Format: rights links user group SIZE MTIME_EPOCH NAME
        # Match only *.tar.gz / *.tar.zst / *.sha256
        if [[ "$line" =~ ^[-rwxd]+\ +[0-9]+\ +[^[:space:]]+\ +[^[:space:]]+\ +([0-9]+)\ +([0-9]+)\ +(.*\.(tar\.gz|tar\.zst|tar\.xz|sha256))$ ]]; then
            SIZE="${BASH_REMATCH[1]}"
            MTIME="${BASH_REMATCH[2]}"
            FNAME="${BASH_REMATCH[3]}"
            if [[ "$MTIME" -lt "$CUTOFF_EPOCH" ]]; then
                if [[ "$DRY_RUN_RAW" == "false" ]]; then
                    lftp -u "${USER},${PASS}" "ftp://${HOST}:${PORT}" <<EOF >/dev/null 2>&1
$PASSIVE_SETTING
set ftp:ssl-allow yes
rm ${REMOTE_PATH}/${FNAME}
bye
EOF
                fi
                PRUNED=$(( PRUNED + 1 ))
                FREED=$(( FREED + SIZE ))
                onx_log "pruned ftp: ${HOST}:${REMOTE_PATH}/${FNAME} ($(_bytes_pretty $SIZE))"
            else
                KEPT=$(( KEPT + 1 ))
            fi
        fi
    done < "$OUT_FILE"
    ;;

# ─── Google Drive (rclone, OAuth) ──────────────────────────────────────────────
google_drive)
    command -v rclone >/dev/null 2>&1 \
        || onx_die 2 "rclone kurulu değil — Google Drive prune için gerekli"

    CLIENT_ID="$(onx_json_get "$CONFIG" client_id '')"
    CLIENT_SECRET="$(onx_json_get "$CONFIG" client_secret '')"
    REFRESH_TOKEN="$(onx_json_get "$CONFIG" refresh_token '')"
    FOLDER_ID="$(onx_json_get "$CONFIG" folder_id '')"

    [[ -z "$REFRESH_TOKEN" ]] && onx_die 1 "config.refresh_token zorunlu"

    TOKEN_JSON="$(jq -nc --arg rt "$REFRESH_TOKEN" \
        '{access_token:"",token_type:"Bearer",refresh_token:$rt,expiry:"2000-01-01T00:00:00Z"}')"
    export RCLONE_CONFIG_GDRIVE_TYPE="drive"
    export RCLONE_CONFIG_GDRIVE_SCOPE="drive"
    export RCLONE_CONFIG_GDRIVE_CLIENT_ID="$CLIENT_ID"
    export RCLONE_CONFIG_GDRIVE_CLIENT_SECRET="$CLIENT_SECRET"
    export RCLONE_CONFIG_GDRIVE_TOKEN="$TOKEN_JSON"
    # folder_id gerçek Drive ID hash'i mi (20+ char) yoksa klasör ADI mı? — PUSH ile aynı mantık.
    # ID ise root_folder_id; isim ise yol (gdrive:NAME/). Aksi halde ismi ID sanıp 404 verir
    # (push gdrive:ONOXSOFT/ yoluna yazıyor; prune root_folder_id=ONOXSOFT sanıp 404 alıyordu).
    GD_REMOTE="gdrive:"
    if [[ -n "$FOLDER_ID" ]]; then
        if [[ "$FOLDER_ID" =~ ^[A-Za-z0-9_-]{20,}$ ]]; then
            export RCLONE_CONFIG_GDRIVE_ROOT_FOLDER_ID="$FOLDER_ID"
        else
            GD_FOLDER="$(printf '%s' "$FOLDER_ID" | tr -cd 'A-Za-z0-9 ._-')"
            GD_REMOTE="gdrive:${GD_FOLDER}/"
        fi
    fi

    TOOL="rclone"
    DRY_FLAG="$(_dry_flag)"
    OUT_FILE="$(mktemp /tmp/onx-rclone-gdrive-prune-XXXXXX)"
    trap 'rm -f "$OUT_FILE"' EXIT

    rclone delete \
        --min-age "${RETENTION_DAYS}d" \
        --include '*.tar.gz' --include '*.tar.zst' --include '*.tar.xz' \
        --include '*.sha256' \
        $DRY_FLAG \
        --stats=0 \
        "$GD_REMOTE" \
        >"$OUT_FILE" 2>&1 \
        || onx_die 3 "rclone Drive prune başarısız: $(tail -c 500 "$OUT_FILE")"

    # grep -c 0-eşleşmede "0" yazıp exit 1 döner → eski "|| echo 0" 2. bir "0" ekleyip
    # $PRUNED'i "0\n0" yapıyordu → jq --argjson "invalid JSON" patlatıyordu. Fallback'i
    # substitution DIŞINA al: 0-eşleşme/hata → PRUNED=0 (tek tamsayı). dry-run'da hiçbir
    # şey silinmeyince PRUNED=0 yaygın hal → bu yüzden prune fiilen hep patlıyordu.
    PRUNED=$(grep -cE '(Deleted|Would delete)' "$OUT_FILE" 2>/dev/null) || PRUNED=0
    FREED=0
    KEPT=$(rclone size "$GD_REMOTE" --json 2>/dev/null | jq -r '.count // 0')

    # Per-gün klasörler (push tarafı gdrive:<klasör>/YYYY-MM-DD/ yazıyor) tüm yedekleri
    # budanınca boş kalır; rmdirs ile temizle (kökü koru). Dry-run'da atla.
    if [[ -z "$DRY_FLAG" ]]; then
        rclone rmdirs "$GD_REMOTE" --leave-root >/dev/null 2>&1 || true
    fi
    ;;

# ─── Restic ────────────────────────────────────────────────────────────────────
restic)
    require_cmd restic
    TOOL="restic"
    REPO="$(onx_json_get "$CONFIG" repo '')"
    PASSWORD="$(onx_json_get "$CONFIG" password '')"
    [[ -z "$REPO" ]] && onx_die 1 "config.repo zorunlu"
    [[ -z "$PASSWORD" ]] && onx_die 1 "config.password zorunlu"
    export RESTIC_PASSWORD="$PASSWORD"
    # restic-native retention: son RETENTION_DAYS günü tut, gerisini forget (dedup-aware prune).
    if [[ "$DRY_RUN_RAW" == "true" ]]; then
        FORGET_OUT="$(restic -r "$REPO" forget --keep-within "${RETENTION_DAYS}d" --dry-run 2>&1 || true)"
    else
        FORGET_OUT="$(restic -r "$REPO" forget --keep-within "${RETENTION_DAYS}d" --prune 2>&1 || true)"
    fi
    # 'remove N snapshots' satırı (yoksa boş) — grup + '|| true' set -e/pipefail abort'unu önler;
    # sayıya çevirme aşağıdaki evrensel sanitize'da (tr -cd) yapılır.
    PRUNED="$( { printf '%s' "$FORGET_OUT" | grep -oE 'remove [0-9]+ snapshots' | head -1; } 2>/dev/null || true )"
    KEPT=$(restic -r "$REPO" snapshots --json 2>/dev/null | jq 'length' 2>/dev/null || echo 0)
    FREED=0
    ;;

*)
    onx_die 1 "Bilinmeyen tip: ${TYPE}"
    ;;
esac

END_S=$(date +%s)
DURATION=$(( END_S - START_S ))

# Sayaçları final jq'a girmeden TEMİZLE — SFTP wc -l / rclone çıktısı CR/boşluk içerebilir;
# '--argjson' bunda "invalid JSON" ile patlardı → prune sildiği hâlde "başarısız" raporluyordu (referee #3).
PRUNED=$(printf '%s' "${PRUNED:-0}" | tr -cd '0-9'); PRUNED=${PRUNED:-0}
KEPT=$(printf '%s' "${KEPT:-0}" | tr -cd '0-9'); KEPT=${KEPT:-0}
FREED=$(printf '%s' "${FREED:-0}" | tr -cd '0-9'); FREED=${FREED:-0}

onx_audit "onx-backup" "dest-prune type=${TYPE} tool=${TOOL} pruned=${PRUNED} kept=${KEPT} freed=${FREED} dry_run=${DRY_RUN_RAW}"

DRY_JSON="false"
[[ "$DRY_RUN_RAW" == "true" ]] && DRY_JSON="true"

jq -nc \
    --argjson pruned "$PRUNED" \
    --argjson freed "$FREED" \
    --argjson kept "${KEPT:-0}" \
    --argjson dry "$DRY_JSON" \
    --arg tool "$TOOL" \
    --argjson dur "$DURATION" \
    '{pruned:$pruned,freed_bytes:$freed,kept:$kept,dry_run:$dry,tool:$tool,duration_seconds:$dur}'
