#!/usr/bin/env bash
set -euo pipefail

# Ubuntu 一键部署/更新脚本。
# - 首次执行会交互式生成 .env.deploy，本机保存，不提交 git。
# - 后续执行会拉取代码、更新依赖、构建前端、安装/重启用户级 systemd 服务。

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.deploy"
PYTHON_VERSION="${PYTHON_VERSION:-3.10.14}"
DEPLOY_INSTANCE="${CLAW_DEPLOY_INSTANCE:-zk-data-agent}"
BACKEND_SERVICE="${CLAW_BACKEND_SERVICE:-${DEPLOY_INSTANCE}-backend}"
FRONTEND_SERVICE="${CLAW_FRONTEND_SERVICE:-${DEPLOY_INSTANCE}-frontend}"
USER_SYSTEMD_DIR="${HOME}/.config/systemd/user"
BRANCH=""
FORCE=0
SKIP_GIT=0
BOOTSTRAP_SYSTEM=0

usage() {
	cat <<EOF
Usage: bash scripts/deploy-ubuntu.sh [branch] [--force] [--skip-git] [--bootstrap-system]

Options:
  branch      要部署的 git 分支；不传则使用当前分支
  --force     使用 git reset --hard origin/<branch> 覆盖本地改动
  --skip-git  跳过 git 拉取，只部署当前工作区
  --bootstrap-system
              强制使用 sudo 安装 Ubuntu 系统依赖；首次安装会默认执行
EOF
}

while [[ $# -gt 0 ]]; do
	case "$1" in
		--force)
			FORCE=1
			shift
			;;
		--skip-git)
			SKIP_GIT=1
			shift
			;;
		--bootstrap-system)
			BOOTSTRAP_SYSTEM=1
			shift
			;;
		-h|--help)
			usage
			exit 0
			;;
		*)
			if [[ -n "${BRANCH}" ]]; then
				echo "Unexpected argument: $1" >&2
				usage >&2
				exit 1
			fi
			BRANCH="$1"
			shift
			;;
	esac
done

log() {
	printf "\n\033[1;34m==>\033[0m %s\n" "$1"
}

warn() {
	printf "\033[1;33mWARN\033[0m %s\n" "$1" >&2
}

fail() {
	printf "\033[1;31mERROR\033[0m %s\n" "$1" >&2
	exit 1
}

configure_instance_names() {
	DEPLOY_INSTANCE="${CLAW_DEPLOY_INSTANCE:-${DEPLOY_INSTANCE:-zk-data-agent}}"
	BACKEND_SERVICE="${CLAW_BACKEND_SERVICE:-${DEPLOY_INSTANCE}-backend}"
	FRONTEND_SERVICE="${CLAW_FRONTEND_SERVICE:-${DEPLOY_INSTANCE}-frontend}"
}

require_command() {
	local name="$1"
	local hint="$2"
	if ! command -v "${name}" >/dev/null 2>&1; then
		fail "缺少命令：${name}。${hint}"
	fi
}

resolve_npm_bin() {
	if [[ -n "${CLAW_NODE_BIN_DIR:-}" ]]; then
		export PATH="${CLAW_NODE_BIN_DIR}:${PATH}"
	fi
	local npm_bin="${CLAW_NPM_BIN:-$(command -v npm || true)}"
	if [[ -z "${npm_bin}" || ! -x "${npm_bin}" ]]; then
		fail "缺少命令：npm。请先安装 Node.js 和 npm，建议 Node.js 20 或 22。"
	fi
	local node_bin_dir
	node_bin_dir="$(dirname "${npm_bin}")"
	export PATH="${node_bin_dir}:${PATH}"
	if ! "${npm_bin}" --version >/dev/null 2>&1; then
		fail "npm 不可用：${npm_bin}。请确认同目录下存在 node，或重新安装 Node.js/npm。"
	fi
	printf "%s\n" "${npm_bin}"
}

bootstrap_system_packages() {
	if [[ "${BOOTSTRAP_SYSTEM}" != "1" && -f "${ENV_FILE}" ]]; then
		return
	fi
	require_command sudo "首次安装系统依赖需要 sudo。"
	if ! command -v apt-get >/dev/null 2>&1; then
		warn "未发现 apt-get，跳过系统依赖安装。请手工安装 git、curl、pyenv、Node.js、npm 和 Python 编译依赖。"
		return
	fi
	log "首次初始化 Ubuntu 系统依赖"
	sudo apt-get update
	sudo apt-get install -y \
		git curl ca-certificates build-essential \
		libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
		libffi-dev liblzma-dev libncursesw5-dev xz-utils tk-dev \
		systemd
}

configure_nginx_upload_limit() {
	if [[ "${BOOTSTRAP_SYSTEM}" != "1" && -f "${ENV_FILE}" ]]; then
		return
	fi
	local script="${ROOT_DIR}/scripts/configure-nginx-upload-limit.sh"
	if [[ -x "${script}" ]]; then
		log "配置 nginx 上传体积限制"
		"${script}" || warn "nginx 上传体积限制配置失败，可稍后手动执行：bash ${script}"
	fi
}

prompt_value() {
	local var_name="$1"
	local prompt="$2"
	local default_value="${3:-}"
	local secret="${4:-0}"
	local value

	if [[ "${secret}" == "1" ]]; then
		read -r -s -p "${prompt}" value
		printf "\n"
	else
		if [[ -n "${default_value}" ]]; then
			read -r -p "${prompt} [${default_value}]: " value
		else
			read -r -p "${prompt}: " value
		fi
	fi
	value="${value:-${default_value}}"
	printf -v "${var_name}" "%s" "${value}"
}

write_env_file() {
	local api_key="$1"
	local base_url="$2"
	local model="$3"
	local backend_host="$4"
	local backend_port="$5"
	local frontend_host="$6"
	local frontend_port="$7"
	local claw_api_url="http://${backend_host}:${backend_port}"

	umask 077
	cat >"${ENV_FILE}" <<EOF
# ZK Data Agent 本机部署配置。
# 该文件包含敏感信息，只保存在部署机器本地，不要提交到 git。

export OPENAI_API_KEY=$(printf "%q" "${api_key}")
export OPENAI_BASE_URL=$(printf "%q" "${base_url}")
export OPENAI_MODEL=$(printf "%q" "${model}")
export OPENAI_TIMEOUT_SECONDS="${OPENAI_TIMEOUT_SECONDS:-3600}"

export CLAW_DEPLOY_INSTANCE=$(printf "%q" "${DEPLOY_INSTANCE}")
export CLAW_BACKEND_HOST=$(printf "%q" "${backend_host}")
export CLAW_BACKEND_PORT=$(printf "%q" "${backend_port}")
export CLAW_FRONTEND_HOST=$(printf "%q" "${frontend_host}")
export CLAW_FRONTEND_PORT=$(printf "%q" "${frontend_port}")
export CLAW_API_URL=$(printf "%q" "${claw_api_url}")
EOF
	chmod 600 "${ENV_FILE}"
}

ensure_env_file() {
	if [[ -f "${ENV_FILE}" ]]; then
		# shellcheck disable=SC1090
		source "${ENV_FILE}"
		configure_instance_names
		if [[ -z "${OPENAI_API_KEY:-}" ]]; then
			fail "${ENV_FILE} 缺少 OPENAI_API_KEY，请编辑该文件补齐。"
		fi
		log "使用本机部署配置：${ENV_FILE}"
		echo "CLAW_DEPLOY_INSTANCE=${DEPLOY_INSTANCE}"
		echo "OPENAI_BASE_URL=${OPENAI_BASE_URL:-}"
		echo "OPENAI_MODEL=${OPENAI_MODEL:-}"
		echo "OPENAI_TIMEOUT_SECONDS=${OPENAI_TIMEOUT_SECONDS:-3600}"
		echo "OPENAI_API_KEY=已配置"
		return
	fi

	if [[ ! -t 0 ]]; then
		fail "首次部署需要交互式输入 OPENAI_API_KEY。请登录机器后执行：bash scripts/deploy-ubuntu.sh"
	fi

	log "首次部署：生成本机配置 ${ENV_FILE}"
	echo "配置会写入 ${ENV_FILE}，该文件已被 .gitignore 忽略。"

	local api_key base_url model backend_host backend_port frontend_host frontend_port
	prompt_value api_key "请输入 OPENAI_API_KEY: " "" 1
	[[ -n "${api_key}" ]] || fail "OPENAI_API_KEY 不能为空。"
	prompt_value base_url "请输入 OPENAI_BASE_URL" "http://model.mify.ai.srv/v1"
	prompt_value model "请输入 OPENAI_MODEL" "xiaomi/mimo-v2-flash"
	configure_instance_names
	prompt_value backend_host "请输入后端监听地址" "127.0.0.1"
	prompt_value backend_port "请输入后端端口" "8765"
	prompt_value frontend_host "请输入前端监听地址" "0.0.0.0"
	prompt_value frontend_port "请输入前端端口" "3000"
	write_env_file "${api_key}" "${base_url}" "${model}" "${backend_host}" "${backend_port}" "${frontend_host}" "${frontend_port}"
	echo "已写入 ${ENV_FILE}，后续修改配置请编辑这个文件。"
}

update_git() {
	if [[ "${SKIP_GIT}" == "1" ]]; then
		log "跳过 git 拉取，使用当前工作区"
		return
	fi
	require_command git "请先安装 git。"
	cd "${ROOT_DIR}"
	[[ -d .git ]] || fail "${ROOT_DIR} 不是 git 仓库。"

	if [[ -z "${BRANCH}" ]]; then
		BRANCH="$(git branch --show-current)"
	fi
	[[ -n "${BRANCH}" ]] || fail "无法判断当前分支，请显式传入分支名。"

	log "更新代码：origin/${BRANCH}"
	git fetch origin
	git checkout "${BRANCH}"
	if [[ "${FORCE}" == "1" ]]; then
		git reset --hard "origin/${BRANCH}"
	else
		if [[ -n "$(git status --porcelain)" ]]; then
			fail "工作区有未提交改动。请先处理，或使用 --force 覆盖。"
		fi
		git pull --ff-only origin "${BRANCH}"
	fi
}

ensure_python() {
	require_command pyenv "请先安装 pyenv，并安装 Python ${PYTHON_VERSION}。"
	log "准备 Python 环境：${PYTHON_VERSION}"
	cd "${ROOT_DIR}"
	if ! pyenv versions --bare | grep -Fx "${PYTHON_VERSION}" >/dev/null 2>&1; then
		echo "pyenv 未发现 Python ${PYTHON_VERSION}，开始安装。"
		pyenv install "${PYTHON_VERSION}"
	fi
	PYENV_VERSION="${PYTHON_VERSION}" pyenv exec python -m venv "${ROOT_DIR}/.venv"
	"${ROOT_DIR}/.venv/bin/python" -m pip install --upgrade pip setuptools wheel
	"${ROOT_DIR}/.venv/bin/python" -m pip install -e .
}

build_frontend() {
	local npm_bin
	npm_bin="$(resolve_npm_bin)"
	export PATH="$(dirname "${npm_bin}"):${PATH}"
	log "安装并构建前端"
	cd "${ROOT_DIR}/frontend/app"
	if [[ -f package-lock.json ]]; then
		"${npm_bin}" ci
	else
		"${npm_bin}" install
	fi
	"${npm_bin}" run build
}

remember_node_runtime() {
	local npm_bin npx_bin node_bin node_bin_dir tmp_file
	npm_bin="$(resolve_npm_bin)"
	node_bin_dir="$(dirname "${npm_bin}")"
	export PATH="${node_bin_dir}:${PATH}"
	node_bin="$(PATH="${node_bin_dir}:${PATH}" command -v node || true)"
	npx_bin="$(PATH="${node_bin_dir}:${PATH}" command -v npx || true)"
	tmp_file="$(mktemp)"
	grep -v -E '^(# systemd 用户服务不会读取交互式 shell 配置|export CLAW_(NPM_BIN|NPX_BIN|NODE_BIN|NODE_BIN_DIR)=)' "${ENV_FILE}" >"${tmp_file}" || true
	cat >>"${tmp_file}" <<EOF

# systemd 用户服务不会读取交互式 shell 配置，这里记录部署时可用的 Node.js/npm/npx 路径。
export CLAW_NPM_BIN=$(printf "%q" "${npm_bin}")
export CLAW_NPX_BIN=$(printf "%q" "${npx_bin}")
export CLAW_NODE_BIN=$(printf "%q" "${node_bin}")
export CLAW_NODE_BIN_DIR=$(printf "%q" "${node_bin_dir}")
EOF
	install -m 0600 "${tmp_file}" "${ENV_FILE}"
	rm -f "${tmp_file}"
	log "记录 Node.js 运行路径"
	echo "npm=${npm_bin}"
	echo "npx=${npx_bin:-未检测到}"
	echo "node=${node_bin:-未检测到}"
}

install_systemd_service() {
	local service_name="$1"
	local template_path="$2"
	local target_path="${USER_SYSTEMD_DIR}/${service_name}.service"
	local tmp_path
	tmp_path="$(mktemp)"
	mkdir -p "${USER_SYSTEMD_DIR}"
	sed \
		-e "s#__PROJECT_ROOT__#${ROOT_DIR}#g" \
		-e "s#__BACKEND_SERVICE__#${BACKEND_SERVICE}#g" \
		-e "s#__FRONTEND_SERVICE__#${FRONTEND_SERVICE}#g" \
		"${template_path}" >"${tmp_path}"
	install -m 0644 "${tmp_path}" "${target_path}"
	rm -f "${tmp_path}"
}

install_services() {
	require_command systemctl "当前系统不支持 systemd，无法安装服务。"
	log "安装/更新用户级 systemd 服务"
	install_systemd_service "${BACKEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/zk-data-agent-backend.service.template"
	install_systemd_service "${FRONTEND_SERVICE}" "${ROOT_DIR}/deploy/systemd/zk-data-agent-frontend.service.template"
	systemctl --user daemon-reload
	systemctl --user enable "${BACKEND_SERVICE}" "${FRONTEND_SERVICE}"
}

restart_services() {
	log "重启服务"
	systemctl --user restart "${BACKEND_SERVICE}"
	systemctl --user restart "${FRONTEND_SERVICE}"
}

health_check() {
	# shellcheck disable=SC1090
	source "${ENV_FILE}"
	local backend_url="http://${CLAW_BACKEND_HOST:-127.0.0.1}:${CLAW_BACKEND_PORT:-8765}/api/state"
	local frontend_url="http://127.0.0.1:${CLAW_FRONTEND_PORT:-3000}/"
	log "健康检查"
	for _ in {1..40}; do
		if curl -fsS "${backend_url}" >/dev/null 2>&1 && curl -fsS "${frontend_url}" >/dev/null 2>&1; then
			echo "backend: ${backend_url}"
			echo "frontend: http://<机器IP>:${CLAW_FRONTEND_PORT:-3000}/"
			echo "服务已启动。"
			return
		fi
		sleep 0.5
	done
	warn "健康检查失败，输出最近日志。"
	journalctl --user -u "${BACKEND_SERVICE}" -n 80 --no-pager || true
	journalctl --user -u "${FRONTEND_SERVICE}" -n 80 --no-pager || true
	exit 1
}

main() {
	log "ZK Data Agent Ubuntu 部署"
	bootstrap_system_packages
	configure_nginx_upload_limit
	update_git
	ensure_env_file
	configure_instance_names
	ensure_python
	build_frontend
	remember_node_runtime
	install_services
	restart_services
	health_check
}

main "$@"
