#!/bin/bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ==================== 生产配置区：按服务器实际路径修改 ====================

# JDK 安装目录。留空时使用服务器已有 JAVA_HOME；仍为空时使用 PATH 中的 java。
BACKEND_JAVA_HOME=""

# 后端 Jar 所在目录。生产目录结构为：启动脚本在外层，Jar 位于 backend/ruoyi-admin.jar。
APP_HOME="${SCRIPT_DIR}/backend"

# 后端 Jar 文件名。
JAR_NAME="ruoyi-admin.jar"

# Spring Profile。
SPRING_PROFILES_ACTIVE="uat"

# JVM 参数。
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"

# 额外应用启动参数，例如：--server.port=8080
APP_ARGS=""

# 停止进程等待秒数。
STOP_WAIT_SECONDS=30

# ==================== 以下为脚本逻辑，一般不需要修改 ====================

if [[ "${APP_HOME}" != /* ]]; then
  APP_HOME="${SCRIPT_DIR}/${APP_HOME}"
fi
JAR_PATH="${APP_HOME}/${JAR_NAME}"
RELATIVE_JAR_PATH=""
if [[ "${APP_HOME}" == "${SCRIPT_DIR}/"* ]]; then
  RELATIVE_JAR_PATH="${APP_HOME#${SCRIPT_DIR}/}/${JAR_NAME}"
fi
LOG_DIR="${APP_HOME}/logs"
CONSOLE_LOG="${LOG_DIR}/backend-console.log"
PID_FILE="${LOG_DIR}/backend-java.pid"
APP_MARKER="-Dccdi.backend.prod.home=${APP_HOME}"
JAVA_CMD="java"

timestamp() {
  date "+%Y-%m-%d %H:%M:%S"
}

log_info() {
  printf '[%s] %s\n' "$(timestamp)" "$1"
}

log_error() {
  printf '[%s] %s\n' "$(timestamp)" "$1" >&2
}

usage() {
  cat <<'EOF'
用法: ./start-java-backend-prod.sh [start|stop|restart|status|logs]

默认动作:
  start    先关闭旧后端进程，再启动生产后端 Jar，启动成功后持续输出控制台日志

常用配置:
  配置统一写在脚本顶部“生产配置区”，包括 BACKEND_JAVA_HOME、APP_HOME、SPRING_PROFILES_ACTIVE、JAVA_OPTS。

示例:
  ./start-java-backend-prod.sh restart
EOF
}

ensure_command() {
  local command_name="$1"
  if ! command -v "${command_name}" >/dev/null 2>&1; then
    log_error "缺少命令: ${command_name}"
    exit 1
  fi
}

resolve_java_cmd() {
  local configured_java_home="${BACKEND_JAVA_HOME}"
  if [[ -z "${configured_java_home}" ]]; then
    configured_java_home="${JAVA_HOME:-}"
  fi

  if [[ -n "${configured_java_home}" ]]; then
    configured_java_home="${configured_java_home%/}"
    if [[ ! -x "${configured_java_home}/bin/java" ]]; then
      log_error "配置的 JAVA_HOME 无效，未找到可执行文件: ${configured_java_home}/bin/java"
      exit 1
    fi

    export JAVA_HOME="${configured_java_home}"
    JAVA_CMD="${JAVA_HOME}/bin/java"
  else
    ensure_command "java"
    JAVA_CMD="java"
  fi

  log_info "使用 Java 命令: ${JAVA_CMD}"
}

get_process_table() {
  local process_table
  if ! process_table="$(ps -ef 2>/dev/null)"; then
    log_error "执行 ps -ef 失败，无法扫描旧进程"
    return 1
  fi

  printf '%s\n' "${process_table}"
}

is_managed_pid() {
  local pid="$1"
  if [[ -z "${pid}" ]] || ! kill -0 "${pid}" 2>/dev/null; then
    return 1
  fi

  local process_table
  if ! process_table="$(get_process_table)"; then
    return 1
  fi

  local line
  while IFS= read -r line; do
    set -- ${line}
    if [[ "${2:-}" == "${pid}" ]] && is_backend_process_line "${line}"; then
      return 0
    fi
  done <<<"${process_table}"

  return 1
}

is_backend_process_line() {
  local line="$1"
  [[ "${line}" != *"<defunct>"* ]] || return 1
  [[ "${line}" == *" -jar ${JAR_PATH}"* ]] && return 0
  [[ -n "${RELATIVE_JAR_PATH}" && "${line}" == *" -jar ${RELATIVE_JAR_PATH}"* ]]
}

collect_pids() {
  local all_pids=""
  local pid
  local process_table
  if ! process_table="$(get_process_table)"; then
    return 1
  fi

  if [[ -f "${PID_FILE}" ]]; then
    pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
    if is_managed_pid "${pid}"; then
      all_pids="${all_pids} ${pid}"
    fi
  fi

  local line
  while IFS= read -r line; do
    set -- ${line}
    pid="${2:-}"
    if [[ "${pid}" =~ ^[0-9]+$ ]] && is_backend_process_line "${line}"; then
      all_pids="${all_pids} ${pid}"
    fi
  done <<<"${process_table}"

  local unique_pids=""
  for pid in ${all_pids}; do
    case " ${unique_pids} " in
      *" ${pid} "*) ;;
      *) unique_pids="${unique_pids} ${pid}" ;;
    esac
  done

  xargs <<<"${unique_pids}" 2>/dev/null || true
}

start_backend() {
  resolve_java_cmd

  if [[ ! -f "${JAR_PATH}" ]]; then
    log_error "未找到后端 Jar: ${JAR_PATH}"
    exit 1
  fi

  local running_pids
  running_pids="$(collect_pids)"
  if [[ -n "${running_pids}" ]]; then
    log_error "检测到后端已在运行: ${running_pids}"
    exit 1
  fi

  mkdir -p "${LOG_DIR}"
  printf '\n===== %s start =====\n' "$(timestamp)" >>"${CONSOLE_LOG}"

  local profile_arg=""
  if [[ -n "${SPRING_PROFILES_ACTIVE}" ]]; then
    profile_arg="--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"
  fi

  log_info "开始启动后端 Jar: ${JAR_PATH}"
  nohup "${JAVA_CMD}" "${APP_MARKER}" ${JAVA_OPTS} -jar "${JAR_PATH}" ${profile_arg} ${APP_ARGS} >>"${CONSOLE_LOG}" 2>&1 &
  echo $! >"${PID_FILE}"

  sleep 3

  local starter_pid
  starter_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
  if [[ -z "${starter_pid}" ]] || ! kill -0 "${starter_pid}" 2>/dev/null; then
    log_error "启动命令未保持运行，请检查日志: ${CONSOLE_LOG}"
    exit 1
  fi

  log_info "后端启动完成，PID: ${starter_pid}"
}

stop_backend() {
  local pids
  pids="$(collect_pids)"

  if [[ -z "${pids}" ]]; then
    log_info "未发现运行中的后端进程"
    rm -f "${PID_FILE}"
    return 0
  fi

  log_info "准备停止后端进程: ${pids}"
  local pid
  for pid in ${pids}; do
    kill -TERM "${pid}" 2>/dev/null || true
  done

  local elapsed=0
  local remaining_pids="${pids}"
  while [[ -n "${remaining_pids}" && "${elapsed}" -lt "${STOP_WAIT_SECONDS}" ]]; do
    sleep 1
    elapsed=$((elapsed + 1))
    remaining_pids=""
    for pid in ${pids}; do
      if kill -0 "${pid}" 2>/dev/null; then
        remaining_pids="${remaining_pids} ${pid}"
      fi
    done
    remaining_pids="$(xargs <<<"${remaining_pids}" 2>/dev/null || true)"
  done

  if [[ -n "${remaining_pids}" ]]; then
    log_info "仍有进程未退出，执行强制停止: ${remaining_pids}"
    for pid in ${remaining_pids}; do
      kill -KILL "${pid}" 2>/dev/null || true
    done
  fi

  rm -f "${PID_FILE}"
  log_info "后端停止完成"
}

status_backend() {
  local pids
  pids="$(collect_pids)"
  if [[ -n "${pids}" ]]; then
    log_info "后端正在运行，进程: ${pids}"
    return 0
  fi

  log_info "后端未运行"
}

follow_logs() {
  mkdir -p "${LOG_DIR}"
  touch "${CONSOLE_LOG}"
  log_info "持续输出日志中，按 Ctrl+C 仅退出日志查看，不会停止后端进程"
  tail -n 200 -F "${CONSOLE_LOG}"
}

start_action() {
  stop_backend
  start_backend
  follow_logs
}

main() {
  local action="${1:-start}"
  case "${action}" in
    start)
      start_action
      ;;
    stop)
      stop_backend
      ;;
    restart)
      start_action
      ;;
    status)
      status_backend
      ;;
    logs)
      follow_logs
      ;;
    -h|--help|help)
      usage
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main "$@"
