#!/usr/bin/env bash
#
# Wazuh API Backup
#
# Backs up Wazuh configuration objects via the REST API.
# Exports agents, groups, rules, decoders, CDB lists, manager
# configuration, API users, roles, policies, and cluster status
# to individual JSON files in a dated directory.
#
# Usage:
#   WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh
#   WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh --dry-run
#   WAZUH_USER="wazuh-wui" WAZUH_PASS="wazuh" ./wazuh-api-backup.sh --install
#
# Parameters:
#   --dry-run    Show what would be backed up without writing files
#   --install    Create cron job for daily backup at 3am
#   --help       Show usage
#
# Environment:
#   WAZUH_API         Wazuh API base URL (default: https://localhost:55000)
#   WAZUH_USER        API username (default: wazuh-wui)
#   WAZUH_PASS        API password (default: wazuh)
#   BACKUP_DIR        Base backup directory (default: /backup/wazuh-api)
#   RETENTION_DAYS    Delete backups older than this many days (default: 30)
#   CURL_TIMEOUT      API request timeout in seconds (default: 15)
#
# Author:  Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
# Version: 1.01

set -euo pipefail

# --- Configuration ---
readonly VERSION="1.0"
readonly SCRIPT_NAME="$(basename "$0")"
WAZUH_API="${WAZUH_API:-https://localhost:55000}"
WAZUH_USER="${WAZUH_USER:-wazuh-wui}"
WAZUH_PASS="${WAZUH_PASS:-wazuh}"
BACKUP_DIR="${BACKUP_DIR:-/backup/wazuh-api}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
CURL_TIMEOUT="${CURL_TIMEOUT:-15}"
DRY_RUN=false
TOKEN=""

# Backup endpoints: "api_path output_filename"
readonly ENDPOINTS=(
    "agents?limit=500 agents.json"
    "groups groups.json"
    "rules?limit=500 rules.json"
    "decoders?limit=500 decoders.json"
    "lists cdb-lists.json"
    "manager/configuration manager-config.json"
    "manager/status manager-status.json"
    "security/users users.json"
    "security/roles roles.json"
    "security/policies policies.json"
    "security/rules security-rules.json"
    "cluster/status cluster-status.json"
    "syscheck syscheck-config.json"
    "active-response active-response.json"
)

# Custom rule/decoder files to export (raw XML)
readonly CUSTOM_FILES=(
    "rules/files/local_rules.xml local_rules.xml"
    "decoders/files/local_decoder.xml local_decoder.xml"
)

# --- Functions ---

usage() {
    cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS]

Wazuh API Backup

Options:
  --dry-run     Show what would be backed up without writing files
  --install     Create cron job for daily backup at 3am
  --help        Show this help message

Environment Variables:
  WAZUH_API         Wazuh API base URL (default: https://localhost:55000)
  WAZUH_USER        API username (default: wazuh-wui)
  WAZUH_PASS        API password (default: wazuh)
  BACKUP_DIR        Base backup directory (default: /backup/wazuh-api)
  RETENTION_DAYS    Delete backups older than N days (default: 30)
  CURL_TIMEOUT      Request timeout in seconds (default: 15)

Examples:
  WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME
  WAZUH_API="https://wazuh.example.com:55000" WAZUH_USER="automation" WAZUH_PASS="secret" $SCRIPT_NAME
  WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME --dry-run
  WAZUH_USER="wazuh-wui" WAZUH_PASS="secret" $SCRIPT_NAME --install
EOF
    exit 0
}

check_dependencies() {
    local missing=()
    for cmd in curl jq; do
        if ! command -v "$cmd" &>/dev/null; then
            missing+=("$cmd")
        fi
    done
    if [[ ${#missing[@]} -gt 0 ]]; then
        echo "ERROR: Missing required commands: ${missing[*]}" >&2
        echo "Install with: apt install ${missing[*]}  OR  dnf install ${missing[*]}" >&2
        exit 1
    fi
}

validate_config() {
    # Strip trailing slash
    WAZUH_API="${WAZUH_API%/}"
}

get_token() {
    local response
    response=$(curl -sf -k --max-time "$CURL_TIMEOUT" \
        -u "${WAZUH_USER}:${WAZUH_PASS}" \
        -X POST "${WAZUH_API}/security/user/authenticate" 2>/dev/null) || {
        echo "ERROR: Failed to authenticate to Wazuh API at ${WAZUH_API}" >&2
        echo "Check WAZUH_USER, WAZUH_PASS, and API connectivity" >&2
        exit 1
    }

    TOKEN=$(echo "$response" | jq -r '.data.token // empty')

    if [[ -z "$TOKEN" ]]; then
        echo "ERROR: Authentication succeeded but no token returned" >&2
        exit 1
    fi
}

api_get() {
    local endpoint="$1"
    curl -sf -k --max-time "$CURL_TIMEOUT" \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/json" \
        "${WAZUH_API}/${endpoint}" 2>/dev/null || echo ""
}

backup_endpoint() {
    local endpoint="$1"
    local filename="$2"
    local output_dir="$3"
    local response

    if [[ "$DRY_RUN" == true ]]; then
        printf "  %-40s → %s (dry-run)\n" "$endpoint" "$filename"
        return 0
    fi

    response=$(api_get "$endpoint")

    if [[ -z "$response" ]]; then
        printf "  %-40s → %-30s FAIL\n" "$endpoint" "$filename"
        return 1
    fi

    echo "$response" | jq '.' > "${output_dir}/${filename}" 2>/dev/null || {
        # Not JSON (raw XML file) — write as-is
        echo "$response" > "${output_dir}/${filename}"
    }
    local size
    size=$(du -h "${output_dir}/${filename}" | cut -f1)
    printf "  %-40s → %-30s OK  %s\n" "$endpoint" "$filename" "$size"
    return 0
}

backup_group_configs() {
    local output_dir="$1"
    local groups_response

    if [[ "$DRY_RUN" == true ]]; then
        printf "  %-40s → %s (dry-run)\n" "groups/*/configuration" "group-*-config.json"
        return 0
    fi

    groups_response=$(api_get "groups")

    if [[ -z "$groups_response" ]]; then
        printf "  %-40s → %-30s FAIL\n" "groups/*/configuration" "group configs"
        return 1
    fi

    local group_names
    group_names=$(echo "$groups_response" | jq -r '.data.affected_items[].name' 2>/dev/null)

    if [[ -z "$group_names" ]]; then
        printf "  %-40s → %-30s SKIP (no groups)\n" "groups/*/configuration" "group configs"
        return 0
    fi

    local count=0
    while IFS= read -r group; do
        local config
        config=$(api_get "groups/${group}/configuration")
        if [[ -n "$config" ]]; then
            echo "$config" | jq '.' > "${output_dir}/group-${group}-config.json" 2>/dev/null
            ((count++)) || true
        fi
    done <<< "$group_names"

    printf "  %-40s → %-30s OK  %d groups\n" "groups/*/configuration" "group-*-config.json" "$count"
    return 0
}

cleanup_old_backups() {
    if [[ ! -d "$BACKUP_DIR" ]]; then
        return
    fi

    local removed=0
    while IFS= read -r dir; do
        rm -rf "$dir"
        ((removed++)) || true
    done < <(find "$BACKUP_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +"$RETENTION_DAYS" 2>/dev/null)

    if [[ $removed -gt 0 ]]; then
        echo "Retention: removed $removed backup(s) older than ${RETENTION_DAYS} days"
    fi
}

install_cron() {
    if [[ $EUID -ne 0 ]]; then
        echo "ERROR: --install requires root" >&2
        exit 1
    fi

    local script_path
    script_path=$(readlink -f "$0")

    cat > /etc/cron.d/wazuh-api-backup <<EOF
# Wazuh API Backup — runs daily at 3am
WAZUH_API=${WAZUH_API}
WAZUH_USER=${WAZUH_USER}
WAZUH_PASS=${WAZUH_PASS}
BACKUP_DIR=${BACKUP_DIR}
RETENTION_DAYS=${RETENTION_DAYS}
0 3 * * * root ${script_path} 2>/dev/null
EOF

    chmod 644 /etc/cron.d/wazuh-api-backup
    echo "Installed cron job: /etc/cron.d/wazuh-api-backup"
    echo "Backups will be written to: ${BACKUP_DIR}/<date>"
}

# --- Main ---

main() {
    # Parse arguments
    for arg in "$@"; do
        case "$arg" in
            --dry-run) DRY_RUN=true ;;
            --install)
                check_dependencies
                validate_config
                install_cron
                exit 0
                ;;
            --help|-h) usage ;;
            *) echo "Unknown option: $arg" >&2; usage ;;
        esac
    done

    check_dependencies
    validate_config

    local today
    today=$(date +%Y%m%d)
    local output_dir="${BACKUP_DIR}/${today}"

    if [[ "$DRY_RUN" == true ]]; then
        echo "Wazuh API Backup v${VERSION} (dry-run)"
        echo "Target: ${output_dir}"
        echo ""
    else
        mkdir -p "$output_dir"
        echo "Wazuh API Backup v${VERSION}"
        echo "Target: ${output_dir}"
        echo ""
    fi

    # Authenticate
    if [[ "$DRY_RUN" != true ]]; then
        get_token
    fi

    local ok=0
    local fail=0

    # Back up standard endpoints
    for entry in "${ENDPOINTS[@]}"; do
        local endpoint filename
        endpoint="${entry%% *}"
        filename="${entry##* }"

        if backup_endpoint "$endpoint" "$filename" "$output_dir"; then
            ((ok++)) || true
        else
            ((fail++)) || true
        fi
    done

    # Back up custom rule/decoder files (raw XML)
    for entry in "${CUSTOM_FILES[@]}"; do
        local endpoint filename
        endpoint="${entry%% *}"
        filename="${entry##* }"

        if backup_endpoint "$endpoint" "$filename" "$output_dir"; then
            ((ok++)) || true
        else
            ((fail++)) || true
        fi
    done

    # Back up per-group configurations
    if backup_group_configs "$output_dir"; then
        ((ok++)) || true
    else
        ((fail++)) || true
    fi

    echo ""

    if [[ "$DRY_RUN" == true ]]; then
        local total=$((${#ENDPOINTS[@]} + ${#CUSTOM_FILES[@]} + 1))
        echo "Dry-run complete: ${total} backup targets"
    else
        local total_size
        total_size=$(du -sh "$output_dir" | cut -f1)
        echo "Complete: ${ok} OK, ${fail} failed, ${total_size} total"

        cleanup_old_backups
    fi
}

main "$@"
