#!/usr/bin/env bash
# =============================================================================
# SAM DB Backup Script
# =============================================================================
# 용도: MySQL 데이터베이스 백업 (mysqldump + gzip)
# 실행: crontab에서 매일 04:30 실행
#   30 4 * * * /home/webservice/api/scripts/backup/sam-db-backup.sh
# =============================================================================

set -euo pipefail

# 스크립트 경로 기준으로 설정 파일 로드
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF_FILE="${SCRIPT_DIR}/backup.conf"

if [[ ! -f "$CONF_FILE" ]]; then
    echo "[FATAL] 설정 파일 없음: $CONF_FILE" >&2
    exit 1
fi

# shellcheck source=backup.conf.example
source "$CONF_FILE"

# =============================================================================
# 변수 초기화
# =============================================================================
TODAY=$(date +%Y-%m-%d)
TIMESTAMP=$(date +%Y%m%d_%H%M)
DAY_OF_WEEK=$(date +%u)  # 1=월 ~ 7=일
DAILY_DIR="${BACKUP_BASE_DIR}/daily/${TODAY}"
WEEKLY_DIR="${BACKUP_BASE_DIR}/weekly"
LOG_DIR=$(dirname "$LOG_FILE")
ERRORS=()
DB_RESULTS=()

# =============================================================================
# 함수 정의
# =============================================================================

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

ensure_dirs() {
    mkdir -p "$DAILY_DIR" "$WEEKLY_DIR" "$LOG_DIR"
}

create_mycnf() {
    local tmp_cnf
    tmp_cnf=$(mktemp /tmp/.my.cnf.XXXXXX)
    chmod 600 "$tmp_cnf"
    cat > "$tmp_cnf" <<MYCNF
[mysqldump]
user=${DB_USER}
password=${DB_PASS}
host=${DB_HOST}
port=${DB_PORT}
MYCNF
    echo "$tmp_cnf"
}

backup_database() {
    local db_name="$1"
    local output_file="${DAILY_DIR}/${db_name}_${TIMESTAMP}.sql.gz"
    local mycnf
    mycnf=$(create_mycnf)

    log "INFO" "백업 시작: ${db_name}"

    if mysqldump \
        --defaults-extra-file="$mycnf" \
        --single-transaction \
        --routines \
        --triggers \
        --quick \
        --lock-tables=false \
        "$db_name" 2>>"$LOG_FILE" | gzip > "$output_file"; then
        rm -f "$mycnf"

        local file_size
        file_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null || echo 0)

        # 최소 크기 검증
        local min_size_var="MIN_SIZE_$(echo "$db_name" | tr '[:lower:]' '[:upper:]')"
        local min_size="${!min_size_var:-0}"

        if [[ "$file_size" -lt "$min_size" ]]; then
            log "ERROR" "백업 파일 크기 부족: ${db_name} (${file_size} bytes < ${min_size} bytes)"
            ERRORS+=("${db_name}: 파일 크기 부족 (${file_size} < ${min_size})")
            DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"$(basename "$output_file")\",\"size_bytes\":${file_size},\"status\":\"size_error\"}")
            return 1
        fi

        log "INFO" "백업 완료: ${db_name} (${file_size} bytes)"
        DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"$(basename "$output_file")\",\"size_bytes\":${file_size},\"status\":\"success\"}")

        # 일요일이면 weekly 복사
        if [[ "$DAY_OF_WEEK" -eq 7 ]]; then
            local weekly_file="${WEEKLY_DIR}/${db_name}_${TIMESTAMP}_week.sql.gz"
            cp "$output_file" "$weekly_file"
            log "INFO" "주간 백업 복사: ${db_name}"
        fi

        return 0
    else
        rm -f "$mycnf"
        log "ERROR" "mysqldump 실패: ${db_name}"
        ERRORS+=("${db_name}: mysqldump 실패")
        DB_RESULTS+=("{\"db\":\"${db_name}\",\"file\":\"\",\"size_bytes\":0,\"status\":\"dump_error\"}")
        rm -f "$output_file"
        return 1
    fi
}

cleanup_old_backups() {
    log "INFO" "오래된 백업 정리 시작"

    # daily: DAILY_RETENTION_DAYS일 초과 디렉토리 삭제
    if [[ -d "${BACKUP_BASE_DIR}/daily" ]]; then
        find "${BACKUP_BASE_DIR}/daily" -mindepth 1 -maxdepth 1 -type d -mtime +"$DAILY_RETENTION_DAYS" -exec rm -rf {} \; 2>>"$LOG_FILE"
        local daily_deleted=$?
        log "INFO" "일간 백업 정리 완료 (${DAILY_RETENTION_DAYS}일 초과 삭제)"
    fi

    # weekly: WEEKLY_RETENTION_DAYS일 초과 파일 삭제
    if [[ -d "$WEEKLY_DIR" ]]; then
        find "$WEEKLY_DIR" -type f -name "*.sql.gz" -mtime +"$WEEKLY_RETENTION_DAYS" -delete 2>>"$LOG_FILE"
        log "INFO" "주간 백업 정리 완료 (${WEEKLY_RETENTION_DAYS}일 초과 삭제)"
    fi
}

write_status_file() {
    local status="success"
    local errors_json="[]"

    if [[ ${#ERRORS[@]} -gt 0 ]]; then
        status="failure"
        # 에러 배열을 JSON 배열로 변환
        errors_json="["
        for i in "${!ERRORS[@]}"; do
            [[ $i -gt 0 ]] && errors_json+=","
            errors_json+="\"${ERRORS[$i]}\""
        done
        errors_json+="]"
    fi

    # databases 객체 구성
    local databases_json="{"
    for i in "${!DB_RESULTS[@]}"; do
        [[ $i -gt 0 ]] && databases_json+=","
        local result="${DB_RESULTS[$i]}"
        local db_name
        db_name=$(echo "$result" | sed 's/.*"db":"\([^"]*\)".*/\1/')
        local file_name
        file_name=$(echo "$result" | sed 's/.*"file":"\([^"]*\)".*/\1/')
        local size
        size=$(echo "$result" | sed 's/.*"size_bytes":\([0-9]*\).*/\1/')
        databases_json+="\"${db_name}\":{\"file\":\"${file_name}\",\"size_bytes\":${size}}"
    done
    databases_json+="}"

    cat > "$STATUS_FILE" <<EOF
{
  "last_run": "$(date '+%Y-%m-%dT%H:%M:%S%z' | sed 's/\(..\)$/:\1/')",
  "status": "${status}",
  "databases": ${databases_json},
  "errors": ${errors_json}
}
EOF

    log "INFO" "상태 파일 기록: ${status}"
}

# =============================================================================
# 메인 실행
# =============================================================================

main() {
    log "INFO" "========== SAM DB 백업 시작 =========="

    ensure_dirs

    local has_error=0

    for db in $DATABASES; do
        if ! backup_database "$db"; then
            has_error=1
        fi
    done

    cleanup_old_backups
    write_status_file

    if [[ $has_error -eq 1 ]]; then
        log "ERROR" "========== SAM DB 백업 완료 (일부 실패) =========="
        exit 1
    else
        log "INFO" "========== SAM DB 백업 완료 (성공) =========="
        exit 0
    fi
}

main "$@"