#!/usr/bin/env bash
#
# onx-modsec-crs-update — OWASP CRS sürüm güncelleme. DRIVER-AWARE (v3.41).
#
#   Apache → /etc/httpd/modsecurity.d/owasp-crs           (CRS 4.x — mod_security2)
#   v3 (nginx/ols/caddy) → /etc/onoxsoft/modsec-rules/owasp-crs-v3  (CRS 3.3.x — v3-uyumlu)
#
# CRS 4.x bazı kuralları libmodsecurity v3'te crash eder; bu yüzden v3 driver'larda
# "latest" = en yeni 3.3.x sürümü, Apache'de "latest" = en yeni 4.x.
#
# Input:  {"version":"4.7.0|latest", "check_only":bool}
# Output: {installed_version, rules_count, reloaded, changelog, driver, ...}

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

readonly BACKUP_BASE="/var/backups/onox/crs"
readonly GITHUB_RELEASES="https://api.github.com/repos/coreruleset/coreruleset/releases"
readonly LOG_TAG="onox-crs-update"

input="$(cat 2>/dev/null || echo '{}')"
echo "$input" | jq -e 'type == "object"' >/dev/null 2>&1 || input='{}'
version="$(echo "$input" | jq -r '.version // "latest"')"
check_only="$(echo "$input" | jq -r '.check_only // false')"

# ── Driver → CRS base + v3 flag ──────────────────────────────────────────────
DRIVER=$(onx_ws_active_driver)
case "$DRIVER" in
    apache|unknown) CRS_BASE="/etc/httpd/modsecurity.d/owasp-crs"; IS_V3=false ;;
    *)              CRS_BASE="${ONX_MODSEC_SHARED_CRS_V3}";          IS_V3=true ;;
esac

# ── Kurulu sürüm tespit ──────────────────────────────────────────────────────
current_version=""
for vf in "$CRS_BASE/crs-setup.conf.example" "$CRS_BASE/crs-setup.conf"; do
    if [[ -f "$vf" ]]; then
        current_version=$(grep -oE 'Core Rule Set ver\.\s*[0-9.]+' "$vf" 2>/dev/null | head -1 | grep -oE '[0-9.]+' || echo "")
        [[ -n "$current_version" ]] && break
    fi
done
if [[ -z "$current_version" ]]; then
    for vf in "$CRS_BASE/VERSION" "$CRS_BASE/CHANGES.md"; do
        if [[ -f "$vf" ]]; then
            current_version=$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null | head -1 || echo "")
            [[ -n "$current_version" ]] && break
        fi
    done
fi
[[ -z "$current_version" ]] && current_version="unknown"

command -v curl >/dev/null 2>&1 || { jq -nc '{ok:false,error:"curl not installed"}' >&2; exit 2; }

# ── Hedef sürüm ──────────────────────────────────────────────────────────────
target_version=""; download_url=""; changelog=""
if [[ "$version" == "latest" || -z "$version" ]]; then
    if [[ "$IS_V3" == "true" ]]; then
        # En yeni 3.3.x (releases newest-first; ilk eşleşen = en yeni 3.3.x)
        api_all=$(curl -sSL --max-time 15 "${GITHUB_RELEASES}?per_page=100" 2>/dev/null || echo "[]")
        target_version=$(echo "$api_all" | jq -r '[.[].tag_name | select(test("^v?3\\.3\\."))][0] // empty' | sed 's/^v//')
        [[ -z "$target_version" ]] && target_version="3.3.7"
        download_url="https://github.com/coreruleset/coreruleset/archive/refs/tags/v${target_version}.tar.gz"
        changelog="v3-uyumlu CRS ${target_version} (3.3.x serisi — libmodsecurity v3)"
    else
        api_response=$(curl -sSL --max-time 15 "${GITHUB_RELEASES}/latest" 2>/dev/null || echo "")
        [[ -z "$api_response" ]] && { jq -nc '{ok:false,error:"GitHub API unreachable"}' >&2; exit 2; }
        target_version=$(echo "$api_response" | jq -r '.tag_name // empty' | sed 's/^v//')
        download_url=$(echo "$api_response" | jq -r '.tarball_url // empty')
        changelog=$(echo "$api_response" | jq -r '.body // ""' | head -c 2000)
    fi
else
    target_version="$version"
    download_url="https://github.com/coreruleset/coreruleset/archive/refs/tags/v${target_version}.tar.gz"
    changelog="Manual install of v${target_version}"
fi
[[ -z "$target_version" ]] && { jq -nc '{ok:false,error:"could not determine target version"}' >&2; exit 2; }

# ── check_only ───────────────────────────────────────────────────────────────
if [[ "$check_only" == "true" ]]; then
    update_available="false"
    [[ "$current_version" != "$target_version" ]] && update_available="true"
    jq -nc \
        --arg installed "$current_version" --arg latest "$target_version" \
        --argjson update_available "$update_available" --arg driver "$DRIVER" \
        --arg url "https://github.com/coreruleset/coreruleset/releases/tag/v${target_version}" \
        '{ok:true, installed_version:$installed, latest_version:$latest, update_available:$update_available, driver:$driver, release_url:$url}'
    exit 0
fi

# ── Full update (root gerekir) ───────────────────────────────────────────────
require_root
[[ "$DRIVER" == "unknown" ]] && onx_die 2 "Aktif web sunucusu tespit edilemedi — CRS update durduruldu"

mkdir -p "$BACKUP_BASE"
backup_dir="$BACKUP_BASE/crs-$(date +%Y%m%d-%H%M%S)-${DRIVER}-${current_version}"
if [[ -d "$CRS_BASE" ]]; then
    cp -a "$CRS_BASE" "$backup_dir"
    logger -t "$LOG_TAG" "Backed up CRS to $backup_dir"
fi

tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
tarball="$tmpdir/crs.tar.gz"
logger -t "$LOG_TAG" "Downloading $download_url"
if ! curl -sSL --max-time 180 -o "$tarball" "$download_url"; then
    jq -nc --arg url "$download_url" '{ok:false,error:"download failed",url:$url}' >&2; exit 2
fi
extract_dir="$tmpdir/extract"; mkdir -p "$extract_dir"
tar -xzf "$tarball" -C "$extract_dir" --strip-components=1 2>/dev/null || { jq -nc '{ok:false,error:"tarball extract failed"}' >&2; exit 3; }
[[ -d "$extract_dir/rules" ]] || { jq -nc '{ok:false,error:"invalid CRS tarball — no rules/ dir"}' >&2; exit 3; }

old_dir="$CRS_BASE.old.$$"; new_dir="$CRS_BASE.new.$$"
mkdir -p "$new_dir"
cp -a "$extract_dir/rules" "$new_dir/"
[[ -f "$extract_dir/crs-setup.conf.example" ]] && cp -a "$extract_dir/crs-setup.conf.example" "$new_dir/"
# crs-setup.conf'u .example'dan üret (driver main config Include eder)
[[ -f "$new_dir/crs-setup.conf.example" && ! -f "$new_dir/crs-setup.conf" ]] && cp -a "$new_dir/crs-setup.conf.example" "$new_dir/crs-setup.conf"
[[ -d "$extract_dir/util" ]] && cp -a "$extract_dir/util" "$new_dir/"
for f in CHANGES.md CHANGES VERSION LICENSE README.md; do
    [[ -f "$extract_dir/$f" ]] && cp -a "$extract_dir/$f" "$new_dir/"
done

mkdir -p "$(dirname "$CRS_BASE")"
[[ -d "$CRS_BASE" ]] && mv "$CRS_BASE" "$old_dir"
mv "$new_dir" "$CRS_BASE"
chmod -R 0644 "$CRS_BASE"/*.conf "$CRS_BASE"/rules/*.conf 2>/dev/null || true
find "$CRS_BASE" -type d -exec chmod 0755 {} \; 2>/dev/null || true

# ── configtest + reload (driver-aware) — fail → rollback ─────────────────────
reloaded=$(onx_ws_modsec_reload "$DRIVER")
if [[ "$reloaded" != "true" ]]; then
    rm -rf "$CRS_BASE"
    [[ -d "$old_dir" ]] && mv "$old_dir" "$CRS_BASE"
    onx_ws_modsec_reload "$DRIVER" >/dev/null 2>&1 || true
    jq -nc --arg driver "$DRIVER" '{ok:false,error:"configtest/reload failed after CRS update — rolled back",driver:$driver}' >&2
    exit 4
fi
rm -rf "$old_dir"

rules_count=0
[[ -d "$CRS_BASE/rules" ]] && rules_count=$(find "$CRS_BASE/rules" -name '*.conf' -exec grep -hE '^[[:space:]]*SecRule' {} \; | wc -l)

logger -t "$LOG_TAG" "Updated CRS driver=$DRIVER: $current_version -> $target_version ($rules_count rules)"
jq -nc \
    --arg from "$current_version" --arg installed "$target_version" \
    --argjson rules_count "$rules_count" --arg changelog "$changelog" \
    --argjson reloaded true --arg backup_dir "$backup_dir" --arg driver "$DRIVER" \
    '{ok:true, from_version:$from, installed_version:$installed, rules_count:$rules_count, changelog:$changelog, reloaded:$reloaded, backup:$backup_dir, driver:$driver}'
