#!/usr/bin/env bash
# =============================================================================
# Meta              : 公共函数与变量库
# Version           : 1.0.0
# Author            : Bash Shell Senior Development Engineer
# License           : MIT
# Description       : 为备份脚本体系提供标准化的日志、远程执行、加解密及云存储管理功能。
# =============================================================================

#------------------------------------------------------------------------------
# 脚本严格模式
# -e: 命令失败时立即退出
# -u: 变量未定义时报错
# -o pipefail: 管道中任一命令失败则整个管道失败
#------------------------------------------------------------------------------
set -euo pipefail
IFS=$'\n\t'

#------------------------------------------------------------------------------
# 全局常量定义区
#------------------------------------------------------------------------------
# > 基础路径配置
readonly SCRIPT_RUN_DIR="/root/wdd/backup"
readonly LOG_DIR="/root/wdd/backup/logs"

# > 通用配置
readonly REMOTE_SSH_PORT="22333"
readonly ENCRYPTION_PASSWORD_7ZIP="SuperWdd.CCC.123" # !!!请务必修改为强密码!!!
readonly RCLONE_REMOTE_REPO="gd-zeaslity:CloneCone-BackUp" # rclone配置的远程仓库名及路径

# > 日志级别常量
readonly LOG_LEVEL_DEBUG=0
readonly LOG_LEVEL_INFO=1
readonly LOG_LEVEL_WARN=2
readonly LOG_LEVEL_ERROR=3

# > 默认日志级别 (可被调用脚本覆盖)
CURRENT_LOG_LEVEL=${LOG_LEVEL_INFO}

# > 颜色输出定义
readonly C_RED='\033[0;31m'
readonly C_GREEN='\033[0;32m'
readonly C_YELLOW='\033[1;33m'
readonly C_BLUE='\033[0;34m'
readonly C_NC='\033[0m'

#------------------------------------------------------------------------------
# 模块依赖检查
#------------------------------------------------------------------------------
if ! command -v 7z &> /dev/null || ! command -v rclone &> /dev/null || ! command -v ssh &> /dev/null; then
  echo -e "${C_RED}[ERROR] Essential commands (7z, rclone, ssh) are not installed. Aborting.${C_NC}" >&2
  exit 1
fi

# =============================================================================
# 函数定义区
# =============================================================================

###
# 功能描述段: 记录标准化的分级日志
# @param level          <string>  日志级别 (DEBUG/INFO/WARN/ERROR)
# @param message        <string>  要记录的日志消息
# @return               <0>       成功
# @require              LOG_DIR, CURRENT_LOG_LEVEL
###
log_message() {
  local level="$1"
  local message="$2"
  local log_level_value
  local log_file

  log_file="${LOG_DIR}/backup_$(date +%Y%m%d).log"
  mkdir -p "${LOG_DIR}"

  case "${level}" in
  "DEBUG") log_level_value=${LOG_LEVEL_DEBUG} ;;
  "INFO")  log_level_value=${LOG_LEVEL_INFO} ;;
  "WARN")  log_level_value=${LOG_LEVEL_WARN} ;;
  "ERROR") log_level_value=${LOG_LEVEL_ERROR} ;;
  *)       log_level_value=${LOG_LEVEL_INFO} ;;
  esac

  if [[ ${CURRENT_LOG_LEVEL} -le ${log_level_value} ]]; then
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local color_prefix="${C_GREEN}"

    case "${level}" in
    "DEBUG") color_prefix="${C_BLUE}" ;;
    "INFO")  color_prefix="${C_GREEN}" ;;
    "WARN")  color_prefix="${C_YELLOW}" ;;
    "ERROR") color_prefix="${C_RED}" ;;
    esac

    # > 格式化日志条目
    local log_entry
    log_entry=$(printf "[%-5s] %s: %s" "${level}" "${timestamp}" "${message}")

    # > 输出到标准输出/错误
    echo -e "${color_prefix}${log_entry}${C_NC}"

    # > INFO及以上级别写入日志文件
    if [[ ${log_level_value} -ge ${LOG_LEVEL_INFO} ]]; then
      echo "${log_entry}" >> "${log_file}"
    fi
  fi
  return 0
}

###
# 功能描述段: 通过SSH在远程主机上安全地执行命令
# @param remote_user    <string>  远程主机用户名
# @param remote_host    <string>  远程主机名或IP地址
# @param remote_command <string>  待执行的命令
# @param ssh_port       <string>  SSH端口 (可选, 默认22333)
# @return               <exit_code> 远程命令的退出码
# @require              REMOTE_SSH_PORT, ssh client
###
execute_remote_command() {
  local remote_user="$1"
  local remote_host="$2"
  local remote_command="$3"
  local ssh_port=${4:-${REMOTE_SSH_PORT}}

  log_message "DEBUG" "Executing on [${remote_user}@${remote_host}:${ssh_port}]: ${remote_command}"

  ssh -p "${ssh_port}" "${remote_user}@${remote_host}" "${remote_command}"
  local exit_code=$?

  if [[ ${exit_code} -ne 0 ]]; then
    log_message "ERROR" "Remote command failed with exit code ${exit_code}."
    return ${exit_code}
  fi

  log_message "DEBUG" "Remote command executed successfully."
  return 0
}


###
# 功能描述段: 使用7zip加密并压缩指定目录
# @param source_directory <string>  需要压缩的源目录路径
# @param archive_path     <string>  生成的加密压缩包完整路径
# @return                 <0>       成功 | >0 失败
# @require                ENCRYPTION_PASSWORD_7ZIP, 7z command
###
encrypt_with_7zip() {
  local source_directory="$1"
  local archive_path="$2"

  if [[ ! -d "${source_directory}" ]]; then
    log_message "ERROR" "Source directory for encryption does not exist: ${source_directory}"
    return 1
  fi

  log_message "INFO" "Encrypting '${source_directory}' to '${archive_path}'..."

  # > -mhe=on: 加密文件头, 防止泄露文件列表
  # > -p: 指定密码
  7z a -mhe=on -p"${ENCRYPTION_PASSWORD_7ZIP}" "${archive_path}" "${source_directory}"/*
  local exit_code=$?

  if [[ ${exit_code} -ne 0 ]]; then
    log_message "ERROR" "7zip encryption failed with exit code ${exit_code}."
    return ${exit_code}
  fi

  log_message "INFO" "Encryption completed successfully."
  return 0
}

###
# 功能描述段: 使用rclone将本地文件复制到远程仓库
# @param source_file          <string>  本地源文件路径
# @param remote_destination   <string>  rclone远程目标路径 (e.g., "google-drive:backup/app1/")
# @return                     <0>       成功 | >0 失败
# @require                    rclone command
###
rclone_copy() {
  local source_file="$1"
  local remote_destination="$2"

  if [[ ! -f "${source_file}" ]]; then
    log_message "ERROR" "Source file for rclone copy does not exist: ${source_file}"
    return 1
  fi

  log_message "INFO" "Copying '${source_file}' to remote '${remote_destination}'..."

  rclone copy -P "${source_file}" "${remote_destination}"
  local exit_code=$?

  if [[ ${exit_code} -ne 0 ]]; then
    log_message "ERROR" "rclone copy failed with exit code ${exit_code}."
    return ${exit_code}
  fi

  log_message "INFO" "rclone copy completed successfully."
  return 0
}


###
# 功能描述段: 控制rclone远程仓库中的副本数量，删除最旧的副本
# @param remote_path    <string>  远程仓库中的目录路径
# @param file_prefix    <string>  需要管理副本数量的文件名前缀
# @param max_replicas   <integer> 允许保留的最大副本数量
# @return               <0>       成功 | >0 失败
# @require              rclone command
###
rclone_control_replicas() {
  local remote_path="$1"
  local file_prefix="$2"
  local max_replicas="$3"

  log_message "INFO" "Checking replicas for '${file_prefix}*' in '${remote_path}'. Max allowed: ${max_replicas}."

  # > 获取远程文件列表及其修改时间
  local remote_files
  remote_files=$(rclone lsf --format "tp" "${remote_path}" | grep "${file_prefix}" || true)

  if [[ -z "${remote_files}" ]]; then
    log_message "INFO" "No remote files found with prefix '${file_prefix}'. Nothing to do."
    return 0
  fi

  local file_count
  file_count=$(echo "${remote_files}" | wc -l)

  if [[ ${file_count} -le ${max_replicas} ]]; then
    log_message "INFO" "Current replica count (${file_count}) is within the limit (${max_replicas})."
    return 0
  fi

  local files_to_delete_count
  files_to_delete_count=$((file_count - max_replicas))
  log_message "WARN" "Exceeding replica limit. Need to delete ${files_to_delete_count} oldest file(s)."

  # > 按时间排序并提取需要删除的文件名
  local files_to_delete
  files_to_delete=$(echo "${remote_files}" | sort -k2 | head -n "${files_to_delete_count}" | awk -F';' '{print $1}')

  for file in ${files_to_delete}; do
    log_message "INFO" "Deleting oldest replica: ${file}"
    rclone deletefile "${remote_path}/${file}"
    if [[ $? -ne 0 ]]; then
      log_message "ERROR" "Failed to delete remote file: ${file}"
      # > 继续尝试删除其他文件，不立即失败
    fi
  done

  log_message "INFO" "Replica control process finished."
  return 0
}

###
# 功能描述段: 清理指定目录下的所有.7z加密压缩包
# @param target_directory <string>  需要清理的目录路径
# @return                 <0>       成功
# @require                find command
###
cleanup_local_encrypted_files() {
  local target_directory="$1"

  log_message "INFO" "Cleaning up local encrypted files (*.7z) in '${target_directory}'..."

  find "${target_directory}" -maxdepth 1 -type f -name "*.7z" -delete

  log_message "INFO" "Local cleanup finished."
  return 0
}
