#!/bin/bash
# ============================================================
# backup_search_db.sh — search.db 分层备份 (2026-09-03 重构)
# 分层原则: 只保不可再生层
#   gov_raw (54万行正文)            = 唯一不可再生 → 每日导出 search_raw_*.db.gz (保留7份)
#   gov_search FTS / gov_bigram    = 可重建 (触发器自动 + rebuild_bigram_inc2.py) → 不进每日包
#   每周日: 全库 VACUUM INTO → search_*.db.gz (保留4份) 服务器本地兜底, OneDrive 周拉取离线
# 配套: 每次运行刷新 search_schema_live.sql (全 schema 快照, 灾难恢复时建库用)
# ============================================================
set -euo pipefail

DB_PATH=/mnt/data/search.db
BACKUP_DIR=/mnt/data/backups/db
RAW_RETENTION=7
FULL_RETENTION=4
TIMESTAMP=$(date +%Y%m%d_%H%M)
LOGFILE=/var/log/backup_search_db.log
DOW=$(date +%u)   # 7 = 周日

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"; }

mkdir -p "$BACKUP_DIR"

# 清理中断残留的未压缩 .db
find "$BACKUP_DIR" -maxdepth 1 -name 'search*.db' -delete 2>/dev/null || true

if [ ! -f "$DB_PATH" ]; then
    log "❌ search.db 不存在: $DB_PATH"
    exit 1
fi
DB_SIZE=$(du -h "$DB_PATH" | cut -f1)
FREE_KB=$(df -Pk /mnt/data | tail -1 | awk '{print $4}')
FREE_GB=$(( FREE_KB / 1024 / 1024 ))

# ── schema 全量快照 (每次刷新, 供恢复建库) ──
sqlite3 "$DB_PATH" ".schema" > "$BACKUP_DIR/search_schema_live.sql" 2>/dev/null || true

# ══════════ 每日层: 只导出 gov_raw ══════════
do_raw_backup() {
    local TMP_DB="${BACKUP_DIR}/search_raw_${TIMESTAMP}.db"
    local GZ="${TMP_DB}.gz"
    local DDL ROWS
    log "──── 每日层: 导出不可再生 gov_raw (源库 ${DB_SIZE}, /mnt/data 可用 ${FREE_GB}G) ────"
    if [ "$FREE_GB" -lt 8 ]; then
        log "❌ 空间不足: 可用 ${FREE_GB}G < 8G, 跳过本次 raw 备份"
        exit 1
    fi
    rm -f "$TMP_DB" "$GZ"
    DDL=$(sqlite3 "$DB_PATH" "SELECT sql FROM sqlite_master WHERE type='table' AND name='gov_raw';")
    if [ -z "$DDL" ]; then log "❌ 取 gov_raw schema 失败"; exit 1; fi
    sqlite3 "$TMP_DB" "$DDL" || { log "❌ 建 raw 库失败"; exit 1; }
    # 单语句快照读导出 + 同行数校验 (触发器不随导: bk 库只有 gov_raw 表)
    ROWS=$(sqlite3 "$DB_PATH" "ATTACH '$TMP_DB' AS bk; INSERT INTO bk.gov_raw SELECT * FROM main.gov_raw; SELECT COUNT(*) FROM bk.gov_raw;" 2>&1) || {
        log "❌ 导出失败: $ROWS"; rm -f "$TMP_DB"; exit 1; }
    log "导出完成: $ROWS 行"
    gzip -c "$TMP_DB" > "$GZ" || { log "❌ gzip 失败"; rm -f "$TMP_DB" "$GZ"; exit 1; }
    rm -f "$TMP_DB"
    log "✅ raw 备份完成: $(basename "$GZ") ($(du -h "$GZ" | cut -f1))"
    ls -1t "$BACKUP_DIR"/search_raw_*.db.gz 2>/dev/null | tail -n +$((RAW_RETENTION+1)) | while read -r old; do
        rm -f "$old"; log "  清理旧 raw: $(basename "$old")"
    done
}

# ══════════ 周日: 全库 VACUUM INTO 兜底 ══════════
do_full_backup() {
    local TMP_DB="${BACKUP_DIR}/search_${TIMESTAMP}.db"
    local GZ="${TMP_DB}.gz"
    local NEED_GB
    log "──── 周日全库兜底 (源库 ${DB_SIZE}, /mnt/data 可用 ${FREE_GB}G) ────"
    NEED_GB=$(( $(stat -c %s "$DB_PATH") / 1024 / 1024 / 1024 * 2 + 2 ))
    if [ "$FREE_GB" -lt "$NEED_GB" ]; then
        log "❌ 空间不足: 可用 ${FREE_GB}G < 需 ${NEED_GB}G, 跳过本次全库备份"
        exit 1
    fi
    rm -f "$TMP_DB" "$GZ"
    if ! sqlite3 "$DB_PATH" "VACUUM INTO '$TMP_DB'"; then
        log "❌ VACUUM INTO 失败"; rm -f "$TMP_DB"; exit 1
    fi
    gzip -c "$TMP_DB" > "$GZ" || { log "❌ gzip 失败"; rm -f "$TMP_DB" "$GZ"; exit 1; }
    rm -f "$TMP_DB"
    log "✅ 全库备份完成: $(basename "$GZ") ($(du -h "$GZ" | cut -f1))"
    ls -1t "$BACKUP_DIR"/search_[0-9]*.db.gz 2>/dev/null | tail -n +$((FULL_RETENTION+1)) | while read -r old; do
        rm -f "$old"; log "  清理旧全库: $(basename "$old")"
    done
}

if [ "$DOW" -eq 7 ]; then
    do_full_backup
else
    do_raw_backup
fi
log "✅ Done"
