#!/usr/bin/env bash
#
# onx-modsec-crs-update — OWASP CRS yeni sürüm indirme + kurulum.
#
# Input: {"version":"4.7.0|latest", "check_only":bool}
# Output: {"installed_version":"...","rules_count":N,"reloaded":true,"changelog":"..."}

set -euo pipefail

readonly CRS_BASE="/etc/httpd/modsecurity.d/owasp-crs"
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 '{}')"
version="$(echo "$input" | jq -r '.version // "latest"')"
check_only="$(echo "$input" | jq -r '.check_only // false')"

# ── Current version ─────────────────────────────────────────────────────────
current_version="unknown"
if [[ -f "$CRS_BASE/crs-setup.conf.example" ]]; then
    current_version=$(grep -oE '#[[:space:]]+ModSecurity Core Rule Set ver\.\s*[0-9.]+' \
        "$CRS_BASE/crs-setup.conf.example" 2>/dev/null \
        | head -1 \
        | grep -oE '[0-9.]+' || echo "unknown")
fi
[[ -z "$current_version" ]] && current_version="unknown"

# ── Determine target version ────────────────────────────────────────────────
target_version=""
download_url=""

if [[ "$version" == "latest" || -z "$version" ]]; then
    # GitHub API ile latest release
    if ! command -v curl &>/dev/null; then
        jq -nc '{ok:false,error:"curl not installed"}' >&2
        exit 2
    fi

    api_response=$(curl -sSL --max-time 15 "$GITHUB_RELEASES/latest" 2>/dev/null || echo "")
    if [[ -z "$api_response" ]]; then
        jq -nc '{ok:false,error:"GitHub API unreachable"}' >&2
        exit 2
    fi

    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)
else
    # Specific version requested
    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

if [[ -z "$target_version" ]]; then
    jq -nc '{ok:false,error:"could not determine target version"}' >&2
    exit 2
fi

# ── Check-only mode ─────────────────────────────────────────────────────────
if [[ "$check_only" == "true" ]]; then
    update_available="false"
    if [[ "$current_version" != "$target_version" && "$current_version" != "unknown" ]]; then
        update_available="true"
    elif [[ "$current_version" == "unknown" ]]; then
        update_available="true"
    fi

    jq -nc \
        --arg installed "$current_version" \
        --arg latest "$target_version" \
        --argjson update_available "$update_available" \
        --arg url "https://github.com/coreruleset/coreruleset/releases/tag/v${target_version}" \
        '{ok:true, installed_version:$installed, latest_version:$latest, update_available:$update_available, release_url:$url}'
    exit 0
fi

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

# 2. Download new version
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

# 3. Extract
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
}

# 4. Validate structure
if [[ ! -d "$extract_dir/rules" ]]; then
    jq -nc '{ok:false,error:"invalid CRS tarball — no rules/ dir"}' >&2
    exit 3
fi

# 5. Atomic swap — move new in place
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/"
[[ -d "$extract_dir/util" ]] && cp -a "$extract_dir/util" "$new_dir/"

if [[ -d "$CRS_BASE" ]]; then
    mv "$CRS_BASE" "$old_dir"
fi
mv "$new_dir" "$CRS_BASE"

# 6. Apache syntax test
if command -v httpd &>/dev/null && ! httpd -t 2>/tmp/onox-crs-err-$$; then
    err="$(cat /tmp/onox-crs-err-$$ 2>/dev/null || echo unknown)"
    rm -f /tmp/onox-crs-err-$$
    # Rollback
    rm -rf "$CRS_BASE"
    mv "$old_dir" "$CRS_BASE"
    jq -nc --arg err "$err" '{ok:false,error:"Apache syntax test failed after update — rolled back",apache_err:$err}' >&2
    exit 4
fi
rm -f /tmp/onox-crs-err-$$

# 7. Reload Apache
reloaded=true
if command -v systemctl &>/dev/null; then
    systemctl reload httpd 2>/dev/null || reloaded=false
fi

# 8. Cleanup old dir
rm -rf "$old_dir"

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

logger -t "$LOG_TAG" "Updated CRS: $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 "$reloaded" \
    --arg backup_dir "$backup_dir" \
    '{ok:true, from_version:$from, installed_version:$installed, rules_count:$rules_count, changelog:$changelog, reloaded:$reloaded, backup:$backup_dir}'
