#!/usr/bin/env bash
# ============================================================
# anytuck —— AnyTuck 中继管理命令
# 由 install.sh 安装到 /usr/local/bin/anytuck，配置写在 /etc/anytuck/config。
# 直接运行 `anytuck` 进入交互菜单；也支持子命令：
#   anytuck info | password | newpass | setpass | status | logs | restart | up | down | uninstall
# ============================================================
set -euo pipefail

CONFIG_FILE="/etc/anytuck/config"

# loadConfig 读取安装时写入的部署信息（项目目录、compose 文件、profile）。
loadConfig() {
	if [[ ! -f "$CONFIG_FILE" ]]; then
		echo "未找到配置 $CONFIG_FILE，请先运行 install.sh 安装。" >&2
		exit 1
	fi
	# shellcheck disable=SC1090
	source "$CONFIG_FILE"
	: "${PROJECT_DIR:?配置缺少 PROJECT_DIR}"
	: "${COMPOSE_FILE:=docker-compose.yml}"
	ENV_FILE="$PROJECT_DIR/.env"
	export COMPOSE_PROFILES="${COMPOSE_PROFILES:-}"
	# NPM 共存模式的外部网络名，docker compose 经环境变量插值到编排文件
	export SHARED_NETWORK="${SHARED_NETWORK:-edge_shared}"
}

# dc 在项目目录内执行 docker compose，自动带上 -f 与 profile。
dc() {
	(cd "$PROJECT_DIR" && docker compose -f "$COMPOSE_FILE" "$@")
}

# getEnv 读取 .env 中某个键的值（取第一处，= 后原样）。
getEnv() {
	grep -E "^$1=" "$ENV_FILE" 2>/dev/null | head -n1 | cut -d= -f2- || true
}

# setEnv 更新（或追加）.env 中某个键；用 | 作 sed 分隔并转义特殊字符。
setEnv() {
	local key="$1" val="$2"
	local escaped
	escaped=$(printf '%s' "$val" | sed -e 's/[\\|&]/\\&/g')
	if grep -qE "^${key}=" "$ENV_FILE"; then
		sed -i "s|^${key}=.*|${key}=${escaped}|" "$ENV_FILE"
	else
		printf '%s=%s\n' "$key" "$val" >>"$ENV_FILE"
	fi
}

# relayAddress 依据 .env 拼出客户端连接地址 wss://<域名>[:端口]/ws。
relayAddress() {
	local domain port
	domain=$(getEnv RELAY_DOMAIN)
	port=$(getEnv RELAY_HTTPS_PORT); port=${port:-443}
	if [[ "$port" == "443" ]]; then
		echo "wss://${domain}/ws"
	else
		echo "wss://${domain}:${port}/ws"
	fi
}

# genToken 生成 32 字节十六进制随机令牌（优先 openssl，回退 /dev/urandom）。
genToken() {
	if command -v openssl >/dev/null 2>&1; then
		openssl rand -hex 32
	else
		head -c32 /dev/urandom | od -An -tx1 | tr -d ' \n'
	fi
}

# applyRelay 让 relay 容器重建以加载新的令牌等环境变量。
applyRelay() {
	echo "→ 重建 relay 容器以生效..."
	dc up -d --force-recreate relay >/dev/null
	echo "✓ 已生效"
}

# showInfo 打印完整连接信息（地址 + 令牌），并在可用时给出二维码。
showInfo() {
	local token addr
	token=$(getEnv RELAY_TOKEN)
	addr=$(relayAddress)
	echo "============================================"
	echo " AnyTuck 客户端中继设置"
	echo "--------------------------------------------"
	echo " 中继地址 : $addr"
	echo " 连接密码 : $token"
	echo "============================================"
	if command -v qrencode >/dev/null 2>&1; then
		echo "（下方二维码内容为地址与密码 JSON，供手动参考）"
		printf '{"relay":"%s","token":"%s"}' "$addr" "$token" | qrencode -t ANSIUTF8
	fi
}

# showPassword 只打印当前连接密码。
showPassword() {
	echo "当前连接密码：$(getEnv RELAY_TOKEN)"
}

# newPassword 随机生成新密码，写入 .env 并重建 relay。
newPassword() {
	local token
	token=$(genToken)
	setEnv RELAY_TOKEN "$token"
	echo "新密码：$token"
	applyRelay
	echo "提示：旧密码立即失效，请到所有客户端更新密码。"
}

# setPassword 交互式输入自定义密码（≥16 字符），写入并重建 relay。
setPassword() {
	local token token2
	read -rsp "输入新密码（≥16 字符）: " token; echo
	read -rsp "再次输入确认      : " token2; echo
	if [[ "$token" != "$token2" ]]; then
		echo "两次输入不一致，已取消。" >&2; return 1
	fi
	if [[ ${#token} -lt 16 ]]; then
		echo "密码过短（<16 字符），已取消。" >&2; return 1
	fi
	setEnv RELAY_TOKEN "$token"
	applyRelay
	echo "提示：旧密码立即失效，请到所有客户端更新密码。"
}

# reconfigure 修改中继域名 / 端口（回车保留原值），写回 .env 并重建生效。
reconfigure() {
	local domain port
	read -rp "完整中继域名 [$(getEnv RELAY_DOMAIN)]: " domain
	read -rp "对外 HTTPS 端口 [$(getEnv RELAY_HTTPS_PORT)]: " port
	[[ -n "$domain" ]] && setEnv RELAY_DOMAIN "$domain"
	[[ -n "$port" ]] && setEnv RELAY_HTTPS_PORT "$port"
	echo "→ 重建以生效..."
	dc up -d --build >/dev/null
	echo "✓ 已更新。新连接地址：$(relayAddress)"
	echo "提示：改域名/端口后记得同步更新 DNS 记录与客户端设置。"
}

# showStatus 打印容器状态。
showStatus() { dc ps; }

# showLogs 跟随查看日志（Ctrl-C 退出）。
showLogs() { dc logs -f --tail=100; }

# restartAll 重启全部服务。
restartAll() { dc restart; echo "✓ 已重启"; }

# serviceUp 启动/重建全部服务。
serviceUp() { dc up -d --build; echo "✓ 已启动"; }

# serviceDown 停止并移除全部容器（数据卷保留，证书不丢）。
serviceDown() { dc down; echo "✓ 已停止"; }

# uninstallAll 卸载：停容器、删 anytuck 命令与配置（询问是否连数据卷一起删）。
uninstallAll() {
	read -rp "确认卸载 AnyTuck 中继？将停止并移除容器 [y/N] " ans
	[[ "${ans,,}" == "y" ]] || { echo "已取消"; return; }
	read -rp "是否同时删除数据卷（证书将丢失）？[y/N] " delvol
	if [[ "${delvol,,}" == "y" ]]; then dc down -v || true; else dc down || true; fi
	rm -f /usr/local/bin/anytuck
	rm -f "$CONFIG_FILE"
	echo "✓ 已卸载 anytuck 命令与配置（项目目录 $PROJECT_DIR 保留，可自行删除）"
}

# menu 打印交互菜单并分发选择。
menu() {
	while true; do
		cat <<'EOF'

========== AnyTuck 中继管理 ==========
 1) 查看连接信息（地址 + 密码）
 2) 只看密码
 3) 随机更换密码
 4) 自定义设置密码
 5) 修改中继域名 / 端口
 6) 查看运行状态
 7) 查看日志（Ctrl-C 退出）
 8) 重启中继
 9) 启动 / 重建
10) 停止
11) 卸载
 0) 退出
======================================
EOF
		read -rp "选择: " choice
		case "$choice" in
			1) showInfo ;;
			2) showPassword ;;
			3) newPassword ;;
			4) setPassword ;;
			5) reconfigure ;;
			6) showStatus ;;
			7) showLogs ;;
			8) restartAll ;;
			9) serviceUp ;;
			10) serviceDown ;;
			11) uninstallAll; exit 0 ;;
			0) exit 0 ;;
			*) echo "无效选择" ;;
		esac
	done
}

loadConfig
# 无参数进菜单；带参数走子命令，便于脚本化调用。
case "${1:-}" in
	"") menu ;;
	info) showInfo ;;
	password|pass) showPassword ;;
	newpass) newPassword ;;
	setpass) setPassword ;;
	reconfig) reconfigure ;;
	status|ps) showStatus ;;
	logs) showLogs ;;
	restart) restartAll ;;
	up) serviceUp ;;
	down) serviceDown ;;
	uninstall) uninstallAll ;;
	*) echo "用法: anytuck [info|password|newpass|setpass|reconfig|status|logs|restart|up|down|uninstall]"; exit 1 ;;
esac
