#!/bin/bash
# =============================================================================
# data-backup.sh -- Backup Creation & Orchestration
# =============================================================================
# Category: data | Bird: raven | Tier: 2 | Node-aware: yes
# Separated from data-restore because creation is safe, restore is dangerous.
#
# Actions:
#   --status            JSON pill (last backup age/health)
#   --run-full          Full system backup
#   --run-db            Database backup (mysqldump/pg_dump)
#   --run-configs       Configuration backup (/etc/binary, nginx, etc.)
#   --run-keys          Key material backup (GPG encrypted)
#   --list              List available backups
#   --rotate            Prune old backups (30+ days)
#   --verify            Verify latest backup checksums
#   --schedule          Show/install backup cron schedule
#   --offsite           Push latest backup to offsite target
#   --send              Run default backup set + verify
#   --digest            Backup health report
#   --flush             Truncate logs
# =============================================================================

TOOL_NAME="data-backup"
TOOL_VERSION="3.0.0"
TOOL_TIER=2
TOOL_BIRD="raven"
TOOL_LOG="/opt/binary/logs/tools/${TOOL_NAME}.log"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "${SCRIPT_DIR}/common.sh" ]]; then source "${SCRIPT_DIR}/common.sh"
elif [[ -f "${SCRIPT_DIR}/../common.sh" ]]; then source "${SCRIPT_DIR}/../common.sh"
else echo "FATAL: common.sh not found" >&2; exit 1; fi
export BIRD_ID="${TOOL_BIRD}"

BACKUP_ROOT="${BACKUP_ROOT:-/opt/binary/backups}"
BACKUP_DB="${BACKUP_ROOT}/db"
BACKUP_CONFIG="${BACKUP_ROOT}/configs"
BACKUP_KEYS="${BACKUP_ROOT}/keys"
BACKUP_FULL="${BACKUP_ROOT}/full"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-30}"
OFFSITE_TARGET="${BACKUP_OFFSITE_TARGET:-}"

for d in "$BACKUP_DB" "$BACKUP_CONFIG" "$BACKUP_KEYS" "$BACKUP_FULL"; do
    mkdir -p "$d" 2>/dev/null
done

_ts_file() { date -u +%Y%m%d-%H%M%S; }

_do_run_db_impl() {
    echo "=== DB Backup: ${NODE_ID} ==="
    local ts; ts=$(_ts_file)
    if command -v mysqldump &>/dev/null; then
        local out="${BACKUP_DB}/mysql-${NODE_ID}-${ts}.sql.gz"
        mysqldump --all-databases --single-transaction --routines --triggers 2>/dev/null | gzip > "$out"
        if [ -s "$out" ]; then
            local size; size=$(stat -c%s "$out" 2>/dev/null)
            sha256sum "$out" >> "${BACKUP_DB}/SHA256SUMS" 2>/dev/null
            echo "  [OK] MySQL: ${out} (${size} bytes)"
            tool_audit "run_db" "type=mysql file=${out} size=${size} node=${NODE_ID}"
        else echo "  [FAIL] MySQL dump empty"; rm -f "$out"; fi
    fi
    if command -v pg_dumpall &>/dev/null; then
        local out="${BACKUP_DB}/pg-${NODE_ID}-${ts}.sql.gz"
        pg_dumpall 2>/dev/null | gzip > "$out"
        if [ -s "$out" ]; then
            echo "  [OK] PostgreSQL: ${out}"
            tool_audit "run_db" "type=pgsql file=${out} node=${NODE_ID}"
        else echo "  [SKIP] PostgreSQL: no data"; rm -f "$out"; fi
    fi
    # SQLite databases
    find /opt/binary/data -name "*.db" -type f 2>/dev/null | while read -r db; do
        local bname; bname=$(basename "$db" .db)
        local out="${BACKUP_DB}/sqlite-${bname}-${NODE_ID}-${ts}.bak"
        sqlite3 "$db" ".backup '${out}'" 2>/dev/null && echo "  [OK] SQLite: ${bname}"
    done
}

_do_run_configs_impl() {
    echo "=== Config Backup: ${NODE_ID} ==="
    local ts; ts=$(_ts_file)
    local out="${BACKUP_CONFIG}/configs-${NODE_ID}-${ts}.tar.gz"
    tar czf "$out" \
        /etc/binary /etc/nginx /etc/wireguard /etc/ssh/sshd_config \
        /etc/fail2ban /etc/firewalld /etc/pdns \
        /etc/postfix /etc/dovecot \
        2>/dev/null
    if [ -s "$out" ]; then
        sha256sum "$out" >> "${BACKUP_CONFIG}/SHA256SUMS" 2>/dev/null
        echo "  [OK] Configs: ${out} ($(stat -c%s "$out" 2>/dev/null) bytes)"
        tool_audit "run_configs" "file=${out} node=${NODE_ID}"
    else echo "  [FAIL] Config backup failed"; fi
}

_do_run_keys_impl() {
    echo "=== Key Backup: ${NODE_ID} ==="
    local ts; ts=$(_ts_file)
    local tmp="/tmp/keys-${NODE_ID}-${ts}.tar"
    local out="${BACKUP_KEYS}/keys-${NODE_ID}-${ts}.tar.gpg"
    tar cf "$tmp" /etc/binary/hmac.key /opt/binary/keys /etc/wireguard 2>/dev/null
    if [ -s "$tmp" ]; then
        gpg --symmetric --cipher-algo AES256 --batch --yes --passphrase-fd 3 \
            -o "$out" "$tmp" 3< <(cat /etc/binary/backup.passphrase 2>/dev/null || echo "365backup") 2>/dev/null
        rm -f "$tmp"
        if [ -s "$out" ]; then
            echo "  [OK] Keys (GPG encrypted): ${out}"
            tool_audit "run_keys" "file=${out} node=${NODE_ID}"
        else echo "  [FAIL] GPG encryption failed"; fi
    else echo "  [SKIP] No key material to backup"; rm -f "$tmp"; fi
}

_do_run_full_impl() {
    echo "=== Full Backup: ${NODE_ID} ==="
    _do_run_db_impl; echo ""
    _do_run_configs_impl; echo ""
    _do_run_keys_impl
    echo ""
    echo "  Full backup complete"
    tool_audit "run_full" "node=${NODE_ID}"
}

_do_list_impl() {
    echo "=== Backups: ${NODE_ID} ==="
    for d in "$BACKUP_DB" "$BACKUP_CONFIG" "$BACKUP_KEYS" "$BACKUP_FULL"; do
        [ -d "$d" ] || continue
        local count; count=$(find "$d" -maxdepth 1 -type f ! -name "SHA256SUMS" 2>/dev/null | wc -l)
        echo "  $(basename "$d"): ${count} file(s)"
        find "$d" -maxdepth 1 -type f ! -name "SHA256SUMS" -printf '    %T+ %s %f\n' 2>/dev/null | sort -r | head -5
    done
}

_do_rotate_impl() {
    echo "=== Rotate: ${NODE_ID} ==="
    local removed=0
    for d in "$BACKUP_DB" "$BACKUP_CONFIG" "$BACKUP_KEYS" "$BACKUP_FULL"; do
        [ -d "$d" ] || continue
        local count; count=$(find "$d" -maxdepth 1 -type f -mtime +${RETENTION_DAYS} ! -name "SHA256SUMS" 2>/dev/null | wc -l)
        find "$d" -maxdepth 1 -type f -mtime +${RETENTION_DAYS} ! -name "SHA256SUMS" -delete 2>/dev/null
        removed=$((removed + count))
    done
    echo "  Removed: ${removed} file(s) older than ${RETENTION_DAYS} days"
    tool_audit "rotate" "removed=${removed} retention=${RETENTION_DAYS} node=${NODE_ID}"
}

_do_verify_impl() {
    echo "=== Verify: ${NODE_ID} ==="
    local pass=0 fail=0
    for d in "$BACKUP_DB" "$BACKUP_CONFIG" "$BACKUP_KEYS"; do
        local sums="${d}/SHA256SUMS"
        [ -f "$sums" ] || continue
        echo "  Checking $(basename "$d")..."
        cd "$d" && sha256sum -c "$sums" 2>/dev/null | tail -5 | sed 's/^/    /'
        pass=$((pass + $(sha256sum -c "$sums" 2>/dev/null | grep -c ": OK")))
        fail=$((fail + $(sha256sum -c "$sums" 2>/dev/null | grep -c ": FAILED")))
    done
    echo "  Passed: ${pass}  Failed: ${fail}"
    tool_audit "verify" "pass=${pass} fail=${fail} node=${NODE_ID}"
    return $fail
}

_do_offsite_impl() {
    echo "=== Offsite Push: ${NODE_ID} ==="
    if [ -z "$OFFSITE_TARGET" ]; then
        echo "  [SKIP] No offsite target configured (set BACKUP_OFFSITE_TARGET)"
        return 0
    fi
    if command -v rclone &>/dev/null; then
        rclone sync "$BACKUP_ROOT" "$OFFSITE_TARGET" --progress 2>&1 | tail -5 | sed 's/^/  /'
    elif command -v rsync &>/dev/null; then
        rsync -avz "$BACKUP_ROOT/" "$OFFSITE_TARGET" 2>&1 | tail -5 | sed 's/^/  /'
    else
        echo "  [FAIL] No sync tool (rclone/rsync) available"
    fi
    tool_audit "offsite" "target=${OFFSITE_TARGET} node=${NODE_ID}"
}

_do_schedule_impl() {
    echo "=== Backup Schedule: ${NODE_ID} ==="
    crontab -l 2>/dev/null | grep -i backup | sed 's/^/  /' || echo "  No backup crons found"
}

_do_send_impl() {
    _do_run_db_impl; echo ""
    _do_run_configs_impl; echo ""
    _do_verify_impl
}

_do_status_impl() {
    local count=0 age="never" s="healthy"
    if [ -d "$BACKUP_ROOT" ]; then
        count=$(find "$BACKUP_ROOT" -type f ! -name "SHA256SUMS" 2>/dev/null | wc -l)
        local latest; latest=$(find "$BACKUP_ROOT" -type f -printf '%T@\n' 2>/dev/null | sort -rn | head -1)
        if [ -n "$latest" ]; then
            local secs=$(( $(date +%s) - ${latest%.*} ))
            age="${secs}s"
            [ "$secs" -gt 86400 ] && s="degraded"
            [ "$secs" -gt 604800 ] && s="down"
        fi
    else s="down"; fi
    [ "$count" -eq 0 ] && s="down"
    echo "{\"tool\":\"${TOOL_NAME}\",\"node\":\"${NODE_ID}\",\"status\":\"${s}\",\"backup_count\":${count},\"last_age\":\"${age}\",\"checked_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
}

_do_digest_impl() { echo "=== Backup Report: ${NODE_ID} ==="; _do_list_impl; echo ""; _do_verify_impl; }
_do_flush_impl() { [ -f "$TOOL_LOG" ] && truncate -s 0 "$TOOL_LOG"; echo "Flush complete"; }

tool_help() {
    echo "${TOOL_NAME} v${TOOL_VERSION} -- Backup Creation & Orchestration"
    echo ""
    echo "Create:"
    echo "  --run-full       Full system backup"
    echo "  --run-db         Database backup (mysql/pg/sqlite)"
    echo "  --run-configs    Configuration backup"
    echo "  --run-keys       Key material backup (GPG encrypted)"
    echo ""
    echo "Manage:"
    echo "  --list           List available backups"
    echo "  --rotate         Prune old backups (${RETENTION_DAYS}+ days)"
    echo "  --offsite        Push to offsite target"
    echo "  --schedule       Show backup cron schedule"
    echo ""
    echo "Verify:"
    echo "  --verify         Check checksums"
    echo "  --status         JSON pill"
    echo "  --send           Default backup + verify"
    echo "  --digest         Full report"
    echo "  --flush          Truncate log"
}

do_send()   { tool_wrap "send"   _do_send_impl "$@"; }
do_status() { tool_wrap "status" _do_status_impl "$@"; }
do_digest() { tool_wrap "digest" _do_digest_impl "$@"; }
do_flush()  { tool_wrap "flush"  _do_flush_impl "$@"; }

tool_init; trap 'tool_complete' EXIT
case "${1:---help}" in
    --send) shift; do_send "$@" ;; --status) shift; do_status "$@" ;;
    --digest) shift; do_digest "$@" ;; --flush) shift; do_flush "$@" ;;
    --run-full) _do_run_full_impl ;; --run-db) _do_run_db_impl ;;
    --run-configs) _do_run_configs_impl ;; --run-keys) _do_run_keys_impl ;;
    --list) _do_list_impl ;; --rotate) _do_rotate_impl ;;
    --verify) _do_verify_impl ;; --offsite) _do_offsite_impl ;;
    --schedule) _do_schedule_impl ;; --help) tool_help ;;
    *) handle_standard_command "$@" ;;
esac
