#!/bin/sh
set -u

STATE_DIR="/etc/peervpn/state"
SESSION_FILE="$STATE_DIR/session.json"
DEVICE_FILE="$STATE_DIR/device.json"
LIONVPN="/usr/bin/lionvpn"

run_capture() {
	set +e
	output="$("$@" 2>&1)"
	rc=$?
	set +e
	printf '%s\n' "$output"
	printf 'exit_code: %s\n' "$rc"
	return 0
}

run_lionvpn() {
	"$LIONVPN" -session "$SESSION_FILE" -device "$DEVICE_FILE" "$@"
}

validate_nonempty() {
	name="$1"
	value="$2"
	if [ -z "$value" ]; then
		printf '%s is required\n' "$name" >&2
		return 1
	fi
}

validate_plain_value() {
	name="$1"
	value="$2"
	if printf '%s' "$value" | grep -q '[[:cntrl:]]'; then
		printf '%s contains control characters\n' "$name" >&2
		return 1
	fi
}

validate_target() {
	case "$1" in
		''|*[!A-Za-z0-9._-]*)
			printf 'invalid target: %s\n' "$1" >&2
			return 1
			;;
	esac
}

validate_access_host() {
	case "$1" in
		''|*[!A-Za-z0-9._-]*|.*|*.)
			printf 'invalid VPN host: %s\n' "$1" >&2
			return 1
			;;
	esac
}

validate_access_port() {
	case "$1" in
		''|*[!0-9]*)
			printf 'invalid VPN port: %s\n' "$1" >&2
			return 1
			;;
	esac
	if [ "$1" -lt 1 ] || [ "$1" -gt 65535 ]; then
		printf 'invalid VPN port: %s\n' "$1" >&2
		return 1
	fi
}

validate_access_protocol() {
	case "$1" in
		socks|socks5|vless)
			return 0
			;;
		udp)
			if legacy_udp_available; then return 0; fi
			printf 'Legacy UDP runtime is unavailable\n' >&2
			return 1
			;;
		*)
			printf 'invalid access protocol: %s\n' "$1" >&2
			return 1
			;;
	esac
}

connect_args() {
	target="$1"
	case "$target" in
		''|recommended)
			return 0
			;;
		*.*)
			country="${target%%.*}"
			name="${target#*.}"
			case "$name" in
				chatgpt|streaming)
					printf '%s\n' --group "$name" "$country"
					;;
				*)
					printf '%s\n' --city "$name" "$country"
					;;
			esac
			;;
		*)
			printf '%s\n' "$target"
			;;
	esac
}

legacy_udp_available() {
	legacy_binary="$(uci -q get lionvpn.main.legacy_binary 2>/dev/null || true)"
	[ -n "$legacy_binary" ] || legacy_binary="/usr/lib/lionvpn/runtime/peervpn-legacy"
	[ -x "$legacy_binary" ] && [ -f "$legacy_binary" ]
}

print_config() {
	access_url="$(uci -q get lionvpn.main.access_url 2>/dev/null || true)"
	access_protocol="$(uci -q get lionvpn.main.access_protocol 2>/dev/null || true)"
	[ -n "$access_protocol" ] || access_protocol="socks"
	printf 'access_url: %s\n' "$access_url"
	printf 'access_protocol: %s\n' "$access_protocol"
	if legacy_udp_available; then printf 'legacy_udp_available: true\n'; else printf 'legacy_udp_available: false\n'; fi
	printf 'exit_code: 0\n'
}

sanitize_logs() {
	sed -E \
		-e 's#(vless://)[^@[:space:]]+@#\1<redacted>@#g' \
		-e 's#(trojan://)[^@[:space:]]+@#\1<redacted>@#g' \
		-e 's#("?access_token"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?refresh_token"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?token"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?password"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?access_session"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?session_id"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?session"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?device_id"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?install_id"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?uuid"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#("?private_key"?[[:space:]]*[:=][[:space:]]*"?)[^",}[:space:]]+("?)#\1<redacted>\2#g' \
		-e 's#[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}#<email-redacted>#g'
}

print_logs() {
	if ! command -v logread >/dev/null 2>&1; then
		printf 'logs: logread unavailable\n'
		printf 'exit_code: 0\n'
		return 0
	fi

	printf 'logs_begin:\n'
	logread 2>&1 | grep -E 'lionvpn|peervpnd|peervpn|xray|Xray|tun2socks' | tail -n 200 | sanitize_logs
	printf 'logs_end:\n'
	printf 'exit_code: 0\n'
}

login_account() {
	account="$1"
	password="$2"
	auth_url="${3:-}"

	validate_nonempty account "$account" || return 2
	validate_nonempty password "$password" || return 2
	validate_plain_value account "$account" || return 2
	if [ -n "$auth_url" ]; then
		validate_plain_value auth_url "$auth_url" || return 2
		printf '%s\n' "$password" | run_lionvpn login --account "$account" --password-stdin --auth-url "$auth_url"
		return $?
	fi
	printf '%s\n' "$password" | run_lionvpn login --account "$account" --password-stdin
}

logout_account() {
	set +e
	output="$(run_lionvpn logout 2>&1)"
	rc=$?
	printf '%s\n' "$output"
	if [ -f "$SESSION_FILE" ]; then
		mv "$SESSION_FILE" "$SESSION_FILE.logout.bak" 2>/dev/null || rm -f "$SESSION_FILE"
		if [ "$rc" -ne 0 ]; then
			printf 'warning: remote logout failed; local session was cleared\n'
		else
			printf 'local session cleared\n'
		fi
	fi
	return 0
}

set_access_config() {
	host="${1:-}"
	port="${2:-}"
	protocol="${3:-socks}"

	[ -n "$protocol" ] || protocol="socks"
	validate_access_protocol "$protocol" || return 2

	if [ -z "$host" ]; then
		uci -q delete lionvpn.main.access_url 2>/dev/null || true
		uci -q set lionvpn.main.access_protocol="$protocol"
		uci -q commit lionvpn
		/etc/init.d/lionvpn restart >/dev/null 2>&1 || true
		printf 'access_url: \n'
		printf 'access_protocol: %s\n' "$protocol"
		printf 'message: automatic VPN backend selection enabled\n'
		return 0
	fi

	case "$protocol" in
		socks|socks5|vless)
			port="18082"
			;;
		tcp|udp)
			[ -n "$port" ] || port="443"
			;;
	esac
	validate_access_host "$host" || return 2
	validate_access_port "$port" || return 2

	case "$protocol" in
		tcp)
			access_url="tcp://$host:$port"
			;;
		udp)
			access_url="udp://$host:$port"
			;;
		*)
			access_url="https://$host:$port"
			;;
	esac
	uci -q set lionvpn.main.access_url="$access_url"
	uci -q set lionvpn.main.access_protocol="$protocol"
	uci -q commit lionvpn
	/etc/init.d/lionvpn restart >/dev/null 2>&1 || true
	printf 'access_url: %s\n' "$access_url"
	printf 'access_protocol: %s\n' "$protocol"
	printf 'message: VPN backend saved\n'
}

action="${1:-status}"
case "$action" in
	status)
		run_capture "$LIONVPN" status
		;;
	doctor)
		run_capture "$LIONVPN" doctor
		;;
	account)
		run_capture run_lionvpn account
		;;
	config)
		print_config
		;;
	login)
		run_capture login_account "${2:-}" "${3:-}" "${4:-}"
		;;
	logout)
		run_capture logout_account
		;;
	servers)
		run_capture run_lionvpn servers
		;;
	refresh)
		run_capture run_lionvpn refresh
		;;
	connect)
		target="${2:-recommended}"
		if ! validate_target "$target"; then
			printf 'exit_code: 2\n'
			exit 0
		fi
		/etc/init.d/lionvpn start >/dev/null 2>&1 || true
		set -- $(connect_args "$target")
		run_capture run_lionvpn connect "$@"
		;;
	disconnect)
		run_capture "$LIONVPN" disconnect
		;;
	cleanup)
		run_capture "$LIONVPN" cleanup
		;;
	set_access)
		run_capture set_access_config "${2:-}" "${3:-}" "${4:-socks}"
		;;
	service_start)
		run_capture /etc/init.d/lionvpn start
		;;
	service_stop)
		run_capture /etc/init.d/lionvpn stop
		;;
	logs)
		print_logs
		;;
	*)
		printf 'unknown action: %s\n' "$action"
		printf 'exit_code: 2\n'
		;;
esac
