#!/usr/bin/env bash
# =============================================================================
# onx-firewall-threat-merge — MERGED single nft set + chunked apply
#
# Production-grade: 8000+ CIDR güvenli yükleme (8000 limit kaldırılıyor).
#
# Strategy:
#   1. Tüm enabled threat list cache dosyalarını oku (/var/cache/onox/threats/*.txt)
#   2. Tek MERGED set olustur: inet onox onox-threat-merged
#   3. Chunked apply: 1000'lik batch'lerde nft -f, her batch arasında 100ms sleep
#   4. Atomic swap: yeni-set → swap → eski-set delete
#   5. Tek drop rule: ip saddr @onox-threat-merged drop
#
# Avantajlar:
#   - 1 set lookup / packet (önce 5x ayrı set kontrolü vardı)
#   - auto-merge → overlapping CIDR'ler otomatik birleşir
#   - Chunked load → network stack timeout yok
#   - 50K+ CIDR sorunsuz
#
# Input (stdin JSON):
#   { "slugs": ["spamhaus_drop","dshield_top",...], "apply": true }
#
# Output (stdout JSON):
#   { "ok":true, "total_cidrs":N, "merged_set":"onox-threat-merged",
#     "chunks_loaded":N, "duration_sec":N }
#
# Exit codes: 0=ok, 1=bad input, 2=no cache, 3=parse fail, 4=nft fail
# =============================================================================

set -uo pipefail  # NOT: -e KALDIRILDI — production safety

readonly CACHE_DIR="/var/cache/onox/threats"
readonly TABLE="inet onox"
readonly SET_NAME="onox-threat-merged"
readonly SET_NEW="${SET_NAME}-new"
readonly CHAIN="threat_in"
readonly CHUNK_SIZE=1000
readonly CHUNK_DELAY_MS=100

input="$(cat 2>/dev/null || echo '{}')"
slugs="$(echo "$input" | jq -r '.slugs // [] | join(" ")' 2>/dev/null)"
apply="$(echo "$input" | jq -r '.apply // false' 2>/dev/null)"

if [[ -z "$slugs" ]]; then
    jq -nc '{ok:false,error:"slugs array required"}' >&2
    exit 1
fi

started_at=$(date +%s)

# ── 1. Tüm slug'ların CIDR dosyalarını topla ────────────────────────────────
merged_tmp=$(mktemp)
trap 'rm -f "$merged_tmp" "${merged_tmp}.dedup" /tmp/onox-threat-chunk-*' EXIT

found_count=0
for slug in $slugs; do
    cache_file="${CACHE_DIR}/${slug}.txt"
    if [[ ! -r "$cache_file" ]]; then
        continue
    fi
    # Comment lines + empty filter
    grep -vE '^[[:space:]]*[;#]|^[[:space:]]*$' "$cache_file" 2>/dev/null | \
        awk '{print $1}' | \
        grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$' >> "$merged_tmp" 2>/dev/null || true
    found_count=$((found_count + 1))
done

if [[ ! -s "$merged_tmp" ]]; then
    jq -nc --argjson n "$found_count" \
        '{ok:false,error:"no IPv4 CIDRs parsed from any feed",slugs_checked:$n}' >&2
    exit 3
fi

# Dedupe + sort
sort -u "$merged_tmp" > "${merged_tmp}.dedup"
total_cidrs=$(wc -l < "${merged_tmp}.dedup" | tr -d '[:space:]')

if [[ "$apply" != "true" ]]; then
    # Dry-run preview
    jq -nc --argjson n "${total_cidrs:-0}" \
        --argjson chunks "$(( total_cidrs / CHUNK_SIZE + 1 ))" \
        '{ok:true,dry_run:true,total_cidrs:$n,chunks_estimated:$chunks}'
    exit 0
fi

# ── 2. nft base infrastructure ──────────────────────────────────────────────
nft list table $TABLE >/dev/null 2>&1 || nft add table $TABLE 2>/dev/null

# threat_in chain (priority -50, trusted_in'in arkasında)
nft list chain $TABLE $CHAIN >/dev/null 2>&1 || \
    nft "add chain $TABLE $CHAIN { type filter hook input priority -50; }" 2>/dev/null

# ── 3. New set (atomic replacement için) ────────────────────────────────────
nft delete set $TABLE "$SET_NEW" 2>/dev/null || true
nft "add set $TABLE $SET_NEW { type ipv4_addr; flags interval; auto-merge; size 200000; }" 2>/dev/null \
    || {
        jq -nc '{ok:false,error:"nft set creation failed"}' >&2
        exit 4
    }

# ── 4. Chunked load — 1000'lik batch'ler ────────────────────────────────────
chunks_loaded=0
chunks_failed=0
batch=0
while true; do
    chunk_file="/tmp/onox-threat-chunk-${batch}"
    start=$((batch * CHUNK_SIZE + 1))
    end=$(((batch + 1) * CHUNK_SIZE))

    sed -n "${start},${end}p" "${merged_tmp}.dedup" > "$chunk_file"
    [[ ! -s "$chunk_file" ]] && { rm -f "$chunk_file"; break; }

    # nft single command for whole chunk
    {
        printf 'add element %s %s { ' "$TABLE" "$SET_NEW"
        paste -sd',' "$chunk_file"
        printf ' }\n'
    } | nft -f - 2>/dev/null && chunks_loaded=$((chunks_loaded + 1)) \
        || chunks_failed=$((chunks_failed + 1))

    rm -f "$chunk_file"
    batch=$((batch + 1))

    # Network stack nefes alsın (büyük listede)
    [[ "$CHUNK_DELAY_MS" -gt 0 ]] && sleep 0.1
done

# ── 5. Atomic swap: eskisini sil, yenisini ana isme rename ──────────────────
# Önce: drop rule'unu eski set'ten temizle
old_handles=$(nft -a list chain $TABLE $CHAIN 2>/dev/null | \
    grep -oE "@$SET_NAME[^[:alnum:]_-]" | head -10 | wc -l)

# Eski set'i sil
nft list set $TABLE "$SET_NAME" >/dev/null 2>&1 && {
    # Drop rule'u handle ile sil
    nft -a list chain $TABLE $CHAIN 2>/dev/null | grep "@$SET_NAME" | \
        grep -oE 'handle [0-9]+' | awk '{print $2}' | while read h; do
        nft delete rule $TABLE $CHAIN handle "$h" 2>/dev/null
    done
    nft delete set $TABLE "$SET_NAME" 2>/dev/null
}

# Yeni set'i ana isme yap (rename desteklenmeyebilir → fallback)
if ! nft "rename set $TABLE $SET_NEW $SET_NAME" 2>/dev/null; then
    # Fallback: yeni set'i sil + create $SET_NAME + tüm element'leri transfer
    # Bu pahalı yol, sadece rename başarısızsa
    nft "add set $TABLE $SET_NAME { type ipv4_addr; flags interval; auto-merge; size 200000; }" 2>/dev/null
    {
        printf 'add element %s %s { ' "$TABLE" "$SET_NAME"
        paste -sd',' "${merged_tmp}.dedup"
        printf ' }\n'
    } | nft -f - 2>/dev/null || true
    nft delete set $TABLE "$SET_NEW" 2>/dev/null
fi

# Drop rule ekle (yeni set için)
nft -a list chain $TABLE $CHAIN 2>/dev/null | grep -q "@$SET_NAME" || \
    nft "add rule $TABLE $CHAIN ip saddr @$SET_NAME counter drop comment \"onox-threat-merged\"" 2>/dev/null

duration=$(($(date +%s) - started_at))

# ── Result ──────────────────────────────────────────────────────────────────
jq -nc \
    --argjson total "${total_cidrs:-0}" \
    --argjson chunks "${chunks_loaded:-0}" \
    --argjson failed "${chunks_failed:-0}" \
    --argjson duration "$duration" \
    --argjson slugs_used "$found_count" \
    --arg set_name "$SET_NAME" \
    '{ok:true, total_cidrs:$total, merged_set:$set_name, chunks_loaded:$chunks,
      chunks_failed:$failed, slugs_merged:$slugs_used, duration_sec:$duration}'
