#!/bin/bash
# =============================================================================
# Server Monitorizare v2 - Backup System
# =============================================================================
# Backs up:
#   - SQLite database (safe online backup via sqlite3 .backup)
#   - Ansible inventory, playbooks, ansible.cfg, ssh_keys
#   - WMT releases and ansible_settings.json
#   - JSON export of critical device/config data (human-readable, portable)
#
# Usage:
#   ./scripts/backup_system.sh              # run backup now
#   ./scripts/backup_system.sh install-cron # install daily cron job
#   ./scripts/backup_system.sh list         # list existing backups
#   ./scripts/backup_system.sh verify <file> # verify a backup archive
# =============================================================================

set -euo pipefail

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="$(dirname "$SCRIPT_DIR")"
BACKUP_DIR="$APP_DIR/data/backups"
DB_PATH="$APP_DIR/data/enhanced_monitoring.db"
LOG_FILE="$APP_DIR/logs/backup.log"
KEEP_DAILY=7      # keep last 7 daily backups
KEEP_WEEKLY=4     # keep last 4 weekly backups (Sunday)
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
DAY_OF_WEEK=$(date +%u)  # 1=Monday … 7=Sunday
BACKUP_NAME="smv2_backup_${TIMESTAMP}"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log() {
    local level="$1"; shift
    local msg="$*"
    local ts; ts=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$ts] [$level] $msg" | tee -a "$LOG_FILE"
}

require_cmd() {
    command -v "$1" &>/dev/null || { log ERROR "Required command not found: $1"; exit 1; }
}

usage() {
    echo "Usage: $0 [backup|install-cron|list|verify <file>]"
    echo "  (no argument)   Run a full backup now"
    echo "  install-cron    Install daily cron job at 02:00"
    echo "  list            List existing backups with sizes"
    echo "  verify <file>   Verify integrity of a backup archive"
    exit 0
}

# ---------------------------------------------------------------------------
# Subcommand: list
# ---------------------------------------------------------------------------
do_list() {
    echo "=== Existing backups in $BACKUP_DIR ==="
    if ls "$BACKUP_DIR"/*.tar.gz &>/dev/null; then
        ls -lh "$BACKUP_DIR"/*.tar.gz | awk '{print $5, $9}'
    else
        echo "  (no .tar.gz backups found)"
    fi
    echo ""
    if ls "$BACKUP_DIR"/*.bak &>/dev/null; then
        echo "=== Legacy .bak files ==="
        ls -lh "$BACKUP_DIR"/*.bak | awk '{print $5, $9}'
    fi
}

# ---------------------------------------------------------------------------
# Subcommand: verify
# ---------------------------------------------------------------------------
do_verify() {
    local archive="$1"
    [[ -f "$archive" ]] || { echo "File not found: $archive"; exit 1; }
    echo "Verifying: $archive"
    tar -tzf "$archive" | head -30
    echo "..."
    echo "OK: archive appears intact ($(tar -tzf "$archive" | wc -l) entries)"
}

# ---------------------------------------------------------------------------
# Subcommand: install-cron
# ---------------------------------------------------------------------------
do_install_cron() {
    local cron_line="0 2 * * * cd $APP_DIR && bash $SCRIPT_DIR/backup_system.sh >> $APP_DIR/logs/backup_cron.log 2>&1"
    # Check if already installed
    if crontab -l 2>/dev/null | grep -qF "backup_system.sh"; then
        echo "Cron job already installed:"
        crontab -l | grep "backup_system.sh"
        return
    fi
    # Append to crontab
    (crontab -l 2>/dev/null; echo "$cron_line") | crontab -
    echo "Cron job installed: daily at 02:00"
    echo "  $cron_line"
    echo ""
    echo "To remove it later: crontab -e  (and delete the backup_system.sh line)"
}

# ---------------------------------------------------------------------------
# Rotate old backups
# ---------------------------------------------------------------------------
rotate_backups() {
    log INFO "Rotating old backups (keep daily=$KEEP_DAILY, weekly=$KEEP_WEEKLY)..."

    # Weekly backups: files tagged with "_weekly_" — rotate keeping last N
    local weekly_count
    weekly_count=$(find "$BACKUP_DIR" -maxdepth 1 -name 'smv2_backup_*_weekly_*.tar.gz' | wc -l)
    if (( weekly_count > KEEP_WEEKLY )); then
        find "$BACKUP_DIR" -maxdepth 1 -name 'smv2_backup_*_weekly_*.tar.gz' \
            | sort -r | tail -n +"$((KEEP_WEEKLY + 1))" \
            | xargs -r rm -v --
        log INFO "Removed old weekly backups (kept $KEEP_WEEKLY)"
    fi

    # Daily backups: all other tar.gz files (not tagged _weekly_)
    local daily_count
    daily_count=$(find "$BACKUP_DIR" -maxdepth 1 -name 'smv2_backup_*.tar.gz' \
        | grep -v '_weekly_' | wc -l)
    if (( daily_count > KEEP_DAILY )); then
        find "$BACKUP_DIR" -maxdepth 1 -name 'smv2_backup_*.tar.gz' \
            | grep -v '_weekly_' | sort -r \
            | tail -n +"$((KEEP_DAILY + 1))" \
            | xargs -r rm -v --
        log INFO "Removed old daily backups (kept $KEEP_DAILY)"
    fi
}

# ---------------------------------------------------------------------------
# Export critical tables to JSON (portable, readable by restore script)
# ---------------------------------------------------------------------------
export_json() {
    local export_dir="$1"
    log INFO "Exporting critical tables to JSON..."

    python3 - "$DB_PATH" "$export_dir" <<'PYEOF'
import sys, sqlite3, json, os

db_path = sys.argv[1]
out_dir  = sys.argv[2]
os.makedirs(out_dir, exist_ok=True)

conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row

# Tables that are essential for migration (config + operational data)
# Log/stats tables are excluded — they can be huge and are not needed on new server
CRITICAL_TABLES = [
    'devices',
    'inventory_groups',
    'device_inventory_groups',
    'wmt_global_config',
    'wmt_update_requests',
    'message_templates',
]

for table in CRITICAL_TABLES:
    try:
        rows = conn.execute(f"SELECT * FROM {table}").fetchall()
        data = [dict(r) for r in rows]
        with open(os.path.join(out_dir, f"{table}.json"), 'w') as f:
            json.dump(data, f, indent=2, default=str)
        print(f"  Exported {table}: {len(data)} rows")
    except Exception as e:
        print(f"  WARNING: could not export {table}: {e}")

conn.close()
PYEOF
}

# ---------------------------------------------------------------------------
# Main backup routine
# ---------------------------------------------------------------------------
do_backup() {
    require_cmd sqlite3
    require_cmd tar
    require_cmd python3

    mkdir -p "$BACKUP_DIR"
    mkdir -p "$(dirname "$LOG_FILE")"

    # Tag Sunday backups as weekly
    local tag=""
    if [[ "$DAY_OF_WEEK" == "7" ]]; then
        tag="_weekly"
        BACKUP_NAME="smv2_backup_${TIMESTAMP}${tag}"
    fi

    STAGE_DIR=$(mktemp -d)
    trap 'rm -rf "$STAGE_DIR"' EXIT

    log INFO "========================================================"
    log INFO "Starting backup: $BACKUP_NAME"
    log INFO "Stage dir: $STAGE_DIR"

    # ------------------------------------------------------------------
    # 1. Safe SQLite backup (uses SQLite's online backup API — safe while
    #    the application is running, produces a consistent snapshot)
    # ------------------------------------------------------------------
    log INFO "Backing up SQLite database..."
    sqlite3 "$DB_PATH" ".backup '$STAGE_DIR/enhanced_monitoring.db'"
    log INFO "Database backed up ($(du -sh "$STAGE_DIR/enhanced_monitoring.db" | cut -f1))"

    # ------------------------------------------------------------------
    # 2. JSON export of critical tables
    # ------------------------------------------------------------------
    export_json "$STAGE_DIR/json_export"

    # ------------------------------------------------------------------
    # 3. Ansible directory (inventory, playbooks, cfg, ssh_keys)
    #    ssh_keys are included because ansible.cfg references them and
    #    are needed for the new server to operate.
    #    Permissions on ssh_keys (600) are preserved by tar.
    # ------------------------------------------------------------------
    log INFO "Backing up Ansible configuration..."
    cp -a "$APP_DIR/ansible" "$STAGE_DIR/ansible"

    # ------------------------------------------------------------------
    # 4. ansible_settings.json (fallback SSH password, auth mode)
    # ------------------------------------------------------------------
    if [[ -f "$APP_DIR/data/ansible_settings.json" ]]; then
        cp "$APP_DIR/data/ansible_settings.json" "$STAGE_DIR/"
        log INFO "Backed up ansible_settings.json"
    fi

    # ------------------------------------------------------------------
    # 5. WMT releases (zip packages used by playbooks)
    # ------------------------------------------------------------------
    if [[ -d "$APP_DIR/data/wmt_releases" ]] && \
       [[ -n "$(ls "$APP_DIR/data/wmt_releases/" 2>/dev/null)" ]]; then
        cp -a "$APP_DIR/data/wmt_releases" "$STAGE_DIR/wmt_releases"
        log INFO "Backed up wmt_releases ($(du -sh "$STAGE_DIR/wmt_releases" | cut -f1))"
    fi

    # ------------------------------------------------------------------
    # 6. App config (excludes secrets already in ansible_settings.json)
    # ------------------------------------------------------------------
    cp -a "$APP_DIR/config" "$STAGE_DIR/config"

    # ------------------------------------------------------------------
    # 7. Create manifest
    # ------------------------------------------------------------------
    cat > "$STAGE_DIR/BACKUP_MANIFEST.txt" <<EOF
Server Monitorizare v2 - Backup Manifest
=========================================
Backup name    : $BACKUP_NAME
Created at     : $(date '+%Y-%m-%d %H:%M:%S %Z')
Hostname       : $(hostname)
DB size        : $(du -sh "$STAGE_DIR/enhanced_monitoring.db" | cut -f1)
DB devices     : $(sqlite3 "$STAGE_DIR/enhanced_monitoring.db" "SELECT COUNT(*) FROM devices;")
DB inv groups  : $(sqlite3 "$STAGE_DIR/enhanced_monitoring.db" "SELECT COUNT(*) FROM inventory_groups;")
WMT configs    : $(sqlite3 "$STAGE_DIR/enhanced_monitoring.db" "SELECT COUNT(*) FROM wmt_global_config;")
Ansible hosts  : $(grep -c 'ansible_host:' "$STAGE_DIR/ansible/inventory/dynamic_inventory.yaml" 2>/dev/null || echo 0)

Included files:
$(find "$STAGE_DIR" -type f | sed "s|$STAGE_DIR/||" | sort)
EOF

    log INFO "Manifest written"

    # ------------------------------------------------------------------
    # 8. Compress everything into a single archive
    # ------------------------------------------------------------------
    local ARCHIVE="$BACKUP_DIR/${BACKUP_NAME}.tar.gz"
    log INFO "Compressing to $ARCHIVE ..."
    tar -czf "$ARCHIVE" -C "$STAGE_DIR" .
    local size; size=$(du -sh "$ARCHIVE" | cut -f1)
    log INFO "Archive created: $ARCHIVE ($size)"

    # ------------------------------------------------------------------
    # 9. Rotate old backups
    # ------------------------------------------------------------------
    rotate_backups

    log INFO "Backup complete: $ARCHIVE"
    log INFO "========================================================"
    echo ""
    echo "Backup complete: $ARCHIVE ($size)"
}

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------
case "${1:-backup}" in
    backup)         do_backup ;;
    install-cron)   do_install_cron ;;
    list)           do_list ;;
    verify)         [[ -n "${2:-}" ]] || usage; do_verify "$2" ;;
    help|--help|-h) usage ;;
    *)              usage ;;
esac
