#!/usr/bin/env bash
# ============================================================================
# backup-vincent.sh — Comprehensive backup for the Vincent AI brain system
#
# Backs up:
#   • events.db      (SQLite with WAL checkpoint + atomic copy)
#   • events/        (JSON event logs, date-based subdirectories)
#   • config files   (config.py, brain.py, embeddings.py, storage.py)
#   • migrations/    (database migration files)
#
# Usage:  backup-vincent.sh [--test]
#   --test  Dry-run mode: prints what would happen without making changes
#
# Output: ~/backups/brain/YYYY-MM-DD/
# Log:    ~/backups/brain/backup.log
# Retention: 90 days
# ============================================================================
set -euo pipefail

# ── Paths ────────────────────────────────────────────────────────────────────
BRAIN_DIR="$HOME/.hermes/brain"
DB_PATH="$BRAIN_DIR/index/events.db"
EVENTS_DIR="$BRAIN_DIR/events"
CONFIG_FILES=("config.py" "brain.py" "embeddings.py" "storage.py")
MIGRATIONS_DIR="$BRAIN_DIR/migrations"

BACKUP_BASE="$HOME/backups/brain"
BACKUP_DATE="$(date +%Y-%m-%d)"
BACKUP_DIR="$BACKUP_BASE/$BACKUP_DATE"
LOG_FILE="$BACKUP_BASE/backup.log"
RETENTION_DAYS=90

# ── Flags ────────────────────────────────────────────────────────────────────
DRY_RUN=false
if [[ "${1:-}" == "--test" ]]; then
    DRY_RUN=true
fi

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

die() {
    log "ERROR: $*"
    exit 1
}

# ── Pre-flight checks ───────────────────────────────────────────────────────
# Ensure log directory exists (needed even for dry-run so log file can be written)
mkdir -p "$BACKUP_BASE"

log "═══════════════════════════════════════════════════════"
log "Backup started (dry-run=$DRY_RUN)"
log "═══════════════════════════════════════════════════════"

# Verify source paths exist
[[ -f "$DB_PATH" ]] || die "Database not found: $DB_PATH"
log "✓ Database found: $DB_PATH ($(du -h "$DB_PATH" | cut -f1))"

if [[ -d "$EVENTS_DIR" ]]; then
    event_count=$(find "$EVENTS_DIR" -type f -name '*.json' 2>/dev/null | wc -l)
    log "✓ Events directory found: $EVENTS_DIR ($event_count JSON files)"
else
    log "⚠ Events directory not found: $EVENTS_DIR (skipping)"
fi

if [[ -d "$MIGRATIONS_DIR" ]]; then
    mig_count=$(find "$MIGRATIONS_DIR" -type f | wc -l)
    log "✓ Migrations directory found: $MIGRATIONS_DIR ($mig_count files)"
else
    log "⚠ Migrations directory not found: $MIGRATIONS_DIR (skipping)"
fi

for f in "${CONFIG_FILES[@]}"; do
    src="$BRAIN_DIR/$f"
    if [[ -f "$src" ]]; then
        log "✓ Config file found: $src"
    else
        log "⚠ Config file not found: $src (skipping)"
    fi
done

# ── Step 1: Create backup directory ─────────────────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    log "[DRY-RUN] Would create backup directory: $BACKUP_DIR"
else
    mkdir -p "$BACKUP_DIR"
    log "✓ Created backup directory: $BACKUP_DIR"
fi

# ── Step 2: SQLite WAL checkpoint ───────────────────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    log "[DRY-RUN] Would checkpoint WAL for: $DB_PATH"
else
    log "→ Checkpointing SQLite WAL (TRUNCATE)..."
    result=$(sqlite3 "$DB_PATH" "PRAGMA wal_checkpoint(TRUNCATE);" 2>&1)
    log "   WAL checkpoint result: $result"
    log "→ Running VACUUM for compactness..."
    sqlite3 "$DB_PATH" "VACUUM;" 2>&1
    log "✓ Database checkpointed and vacuumed"
fi

# ── Step 3: Atomic copy of events.db ────────────────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    log "[DRY-RUN] Would atomically copy events.db → $BACKUP_DIR/events.db"
else
    tmp_db=$(mktemp "$BACKUP_DIR/events.db.XXXXXX")
    log "→ Copying events.db to temp file..."
    cp -p "$DB_PATH" "$tmp_db"
    # Atomic rename
    mv "$tmp_db" "$BACKUP_DIR/events.db"
    db_size=$(du -h "$BACKUP_DIR/events.db" | cut -f1)
    log "✓ events.db copied atomically ($db_size)"
fi

# ── Step 4: Copy events/ directory ──────────────────────────────────────────
if [[ -d "$EVENTS_DIR" ]]; then
    if [[ "$DRY_RUN" == true ]]; then
        log "[DRY-RUN] Would copy events/ → $BACKUP_DIR/events/"
    else
        log "→ Copying events directory..."
        rsync -a --info=progress2 "$EVENTS_DIR/" "$BACKUP_DIR/events/" 2>/dev/null || \
            cp -r "$EVENTS_DIR" "$BACKUP_DIR/events"
        event_files=$(find "$BACKUP_DIR/events" -type f -name '*.json' | wc -l)
        log "✓ Events copied ($event_files JSON files)"
    fi
fi

# ── Step 5: Copy config files ───────────────────────────────────────────────
for f in "${CONFIG_FILES[@]}"; do
    src="$BRAIN_DIR/$f"
    if [[ -f "$src" ]]; then
        if [[ "$DRY_RUN" == true ]]; then
            log "[DRY-RUN] Would copy $f → $BACKUP_DIR/$f"
        else
            cp -p "$src" "$BACKUP_DIR/$f"
            log "✓ Copied: $f"
        fi
    fi
done

# ── Step 6: Copy migrations/ directory ──────────────────────────────────────
if [[ -d "$MIGRATIONS_DIR" ]]; then
    if [[ "$DRY_RUN" == true ]]; then
        log "[DRY-RUN] Would copy migrations/ → $BACKUP_DIR/migrations/"
    else
        log "→ Copying migrations directory..."
        cp -r "$MIGRATIONS_DIR" "$BACKUP_DIR/migrations"
        mig_files=$(find "$BACKUP_DIR/migrations" -type f | wc -l)
        log "✓ Migrations copied ($mig_files files)"
    fi
fi

# ── Step 7: Create manifest.txt with checksums ──────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    log "[DRY-RUN] Would generate manifest.txt with SHA-256 checksums"
else
    manifest="$BACKUP_DIR/manifest.txt"
    log "→ Generating manifest..."

    {
        echo "# Backup manifest — $BACKUP_DATE"
        echo "# Generated: $(date -Iseconds)"
        echo "# Source brain directory: $BRAIN_DIR"
        echo ""
        echo "# ── Checksums (SHA-256) ──"
        # Find all files in backup dir, sort for deterministic order
        find "$BACKUP_DIR" -type f ! -name 'manifest.txt' -print0 | \
            sort -z | \
            xargs -0 sha256sum 2>/dev/null || true
        echo ""
        echo "# ── Metadata ──"
        echo "backup_date=$BACKUP_DATE"
        echo "source_db=$DB_PATH"
        echo "source_db_size=$(du -h "$DB_PATH" | cut -f1)"
        echo "backup_dir=$BACKUP_DIR"
        echo "total_backup_size=$(du -sh "$BACKUP_DIR" | cut -f1)"
        echo "retention_days=$RETENTION_DAYS"
    } > "$manifest"

    log "✓ Manifest written: $manifest"
fi

# ── Step 8: Cleanup old backups (>90 days) ──────────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    old_dirs=$(find "$BACKUP_BASE" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" 2>/dev/null | wc -l)
    log "[DRY-RUN] Would remove $old_dirs backup(s) older than $RETENTION_DAYS days"
else
    old_dirs=$(find "$BACKUP_BASE" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" 2>/dev/null)
    count=0
    while IFS= read -r dir; do
        [[ -z "$dir" ]] && continue
        rm -rf "$dir"
        log "→ Removed old backup: $dir"
        count=$((count + 1))
    done <<< "$old_dirs"
    if [[ $count -gt 0 ]]; then
        log "✓ Cleaned up $count backup(s) older than $RETENTION_DAYS days"
    else
        log "✓ No old backups to clean up"
    fi
fi

# ── Done ─────────────────────────────────────────────────────────────────────
if [[ "$DRY_RUN" == true ]]; then
    log "═══════════════════════════════════════════════════════"
    log "Backup dry-run completed successfully"
    log "═══════════════════════════════════════════════════════"
else
    total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1)
    log "═══════════════════════════════════════════════════════"
    log "Backup completed successfully"
    log "   Directory: $BACKUP_DIR"
    log "   Size: ${total_size:-unknown}"
    log "═══════════════════════════════════════════════════════"
fi
