#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version           :  202511230709-git
# @@Author           :  Jason Hempstead
# @@Contact          :  jason@casjaysdev.pro
# @@License          :  LICENSE.md
# @@ReadME           :  tor-bandwidth --help
# @@Copyright        :  Copyright: (c) 2025 Jason Hempstead, Casjays Developments
# @@Created          :  Sunday, Nov 23, 2025 07:09 EST
# @@File             :  tor-bandwidth
# @@Description      :  Calculate and set Tor bandwidth settings
# @@Changelog        :  New script
# @@TODO             :  Better documentation
# @@Other            :
# @@Resource         :
# @@Terminal App     :  no
# @@sudo/root        :  no
# @@Template         :  shell/bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2120,SC2155,SC2199,SC2317,SC2329
# - - - - - - - - - - - - - - - - - - - - - - - - -
# script variables
APPNAME="$(basename -- "$0" 2>/dev/null)"
VERSION="202511230709-git"
RUN_USER="$USER"
SET_UID="$(id -u)"
SCRIPT_SRC_DIR="${BASH_SOURCE%/*}"
BANDWIDTH_USAGE_CWD="$(realpath "$PWD")"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# script functions
if [ "$SHOW_RAW" != "true" ]; then
	__printf_color() { printf "%b" "$(tput setaf "${2:-7}" 2>/dev/null)" "$1\n" "$(tput sgr0 2>/dev/null)"; }
else
	# Disable colorization
	__printf_color() { { [ -z "$2" ] || DEFAULT_COLOR=$2; } && printf '%b\n' "$1" | tr -d '\t' | sed '/^%b$/d;s,\x1B\[ 0-9;]*[a-zA-Z],,g'; }
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# check for command
__cmd_exists() { which $1 >/dev/null 2>&1 || return 1; }
__function_exists() { builtin type $1 >/dev/null 2>&1 || return 1; }
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Round to nearest whole number (0.5 rounds up)
__round() {
	local num="$1"
	echo "scale=0; ($num + 0.5) / 1" | bc
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Parse bandwidth value to GB
# Accepts: 1000, 1000GB, 1000G, 1000GBytes, 1TB, 1000MB, 1000M, 1000MBytes
__parse_bandwidth_to_gb() {
	local input="$1"
	local number unit gb_value

	# Extract number and unit
	number=$(echo "$input" | sed 's/[^0-9.]//g')
	unit=$(echo "$input" | sed 's/[0-9. ]//g' | tr '[:lower:]' '[:upper:]')

	# If no unit specified, assume GB
	if [ -z "$unit" ]; then
		echo "$number"
		return 0
	fi

	case "$unit" in
	T | TB | TBYTES)
		gb_value=$(echo "scale=2; $number * 1024" | bc)
		;;
	G | GB | GBYTES)
		gb_value="$number"
		;;
	M | MB | MBYTES)
		gb_value=$(echo "scale=2; $number / 1024" | bc)
		;;
	K | KB | KBYTES)
		gb_value=$(echo "scale=6; $number / 1048576" | bc)
		;;
	*)
		__printf_color "Error: Unknown unit '$unit'. Use T/TB, G/GB, M/MB, or K/KB" "1"
		return 1
		;;
	esac

	echo "$gb_value"
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Calculate bandwidth rate and burst from monthly GB allowance
# Returns: accounting_max_gb rate_kbps burst_kbps
__calculate_bandwidth() {
	local monthly_gb="$1"
	local burst_ratio="${TOR_BANDWIDTH_BURST_RATIO:-2}"

	# Seconds in a month (30 days)
	local seconds_per_month=2592000

	# Convert GB to bytes
	local monthly_bytes=$(echo "scale=0; $monthly_gb * 1073741824" | bc)

	# Calculate rate in KB/s (bytes per second / 1024)
	local rate_kbps=$(echo "scale=2; $monthly_bytes / $seconds_per_month / 1024" | bc)
	rate_kbps=$(__round "$rate_kbps")

	# Calculate burst
	local burst_kbps=$(echo "scale=0; $rate_kbps * $burst_ratio" | bc)
	burst_kbps=$(__round "$burst_kbps")

	# Round the accounting max
	local accounting_max=$(__round "$monthly_gb")

	echo "$accounting_max $rate_kbps $burst_kbps"
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Update config file with bandwidth settings
__update_config() {
	local config_file="$1"
	local accounting_max="$2"
	local rate="$3"
	local burst="$4"

	if [ ! -f "$config_file" ]; then
		__printf_color "Error: Config file not found: $config_file" "1"
		return 1
	fi

	# Update AccountingMax
	if grep -q "^AccountingMax" "$config_file"; then
		sed -i "s|^AccountingMax.*|AccountingMax $accounting_max GBytes|" "$config_file"
	else
		echo "AccountingMax $accounting_max GBytes" >>"$config_file"
	fi

	# Update RelayBandwidthRate
	if grep -q "^RelayBandwidthRate" "$config_file"; then
		sed -i "s|^RelayBandwidthRate.*|RelayBandwidthRate $rate KB|" "$config_file"
	else
		echo "RelayBandwidthRate $rate KB" >>"$config_file"
	fi

	# Update RelayBandwidthBurst
	if grep -q "^RelayBandwidthBurst" "$config_file"; then
		sed -i "s|^RelayBandwidthBurst.*|RelayBandwidthBurst $burst KB|" "$config_file"
	else
		echo "RelayBandwidthBurst $burst KB" >>"$config_file"
	fi

	return 0
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Get current accounting status
__get_accounting_status() {
	local service="$1"
	local state_dir="/data/tor/${service:-server}"
	[ -f "$$state_dir/conn-bi-direct" ] || return 0

	echo "=== Bandwidth Status for $service $(date) ==="
	# Get live stats
	local bytes_read=$(grep -s "bytes-read" "$state_dir/conn-bi-direct" 2>/dev/null | awk '{print $2}')
	local bytes_written=$(grep -s "bytes-written" "$state_dir/conn-bi-direct" 2>/dev/null | awk '{print $2}')
	if [ -n "$bytes_read" ] && [ -n "$bytes_written" ]; then
		local total_gb=$(echo "scale=2; ($bytes_read + $bytes_written) / 1073741824" | bc)
		echo "Total transferred this period: ${total_gb} GB"
	fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Configure bandwidth for a specific service
__configure_service() {
	local service="$1"
	local bandwidth_gb="$2"
	local config_file

	case "$service" in
	server)
		config_file="/config/tor/server/server.conf"
		;;
	bridge)
		config_file="/config/tor/bridge/bridge.conf"
		;;
	relay)
		config_file="/config/tor/relay/relay.conf"
		;;
	exit)
		config_file="/config/tor/exit/exit.conf"
		;;
	*)
		__printf_color "Error: Unknown service '$service'" "1"
		return 1
		;;
	esac

	local result=$(__calculate_bandwidth "$bandwidth_gb")
	local accounting_max=$(echo "$result" | awk '{print $1}')
	local rate=$(echo "$result" | awk '{print $2}')
	local burst=$(echo "$result" | awk '{print $3}')

	__printf_color "Configuring $service: ${accounting_max} GBytes/month, ${rate} KB/s rate, ${burst} KB/s burst" "2"

	__update_config "$config_file" "$accounting_max" "$rate" "$burst"
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Check if a service is enabled
__is_service_enabled() {
	local service="$1"
	local var_name="TOR_$(echo "$service" | tr '[:lower:]' '[:upper:]')_ENABLED"
	local enabled="${!var_name:-yes}"
	[ "$enabled" = "yes" ] && return 0 || return 1
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Main bandwidth configuration
__configure_all_bandwidth() {
	# Default to 1TB if not set
	local total_bandwidth_input="${TOR_TOTAL_BANDWIDTH:-1TB}"
	local num_services=0
	local per_service_gb
	local enabled_services=""

	# Count enabled services
	for service in server bridge relay exit; do
		if __is_service_enabled "$service"; then
			num_services=$((num_services + 1))
			enabled_services="$enabled_services $service"
		fi
	done

	if [ "$num_services" -eq 0 ]; then
		__printf_color "No Tor services enabled, skipping bandwidth configuration" "3"
		return 0
	fi

	# Calculate per-service allocation from total
	local total_gb=$(__parse_bandwidth_to_gb "$total_bandwidth_input")
	if [ $? -ne 0 ]; then
		return 1
	fi
	per_service_gb=$(echo "scale=2; $total_gb / $num_services" | bc)
	__printf_color "Total bandwidth: ${total_gb} GB divided by $num_services enabled services = ${per_service_gb} GB each" "6"

	# Configure each enabled service
	for service in $enabled_services; do
		local var_name="TOR_$(echo "$service" | tr '[:lower:]' '[:upper:]')_TOTAL_BANDWIDTH"
		local service_bandwidth="${!var_name:-}"

		if [ -n "$service_bandwidth" ]; then
			# Per-service override
			local service_gb=$(__parse_bandwidth_to_gb "$service_bandwidth")
			if [ $? -eq 0 ]; then
				__configure_service "$service" "$service_gb"
			fi
		elif [ -n "$per_service_gb" ]; then
			# Use divided total
			__configure_service "$service" "$per_service_gb"
		fi
	done
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
__require_bc() {
	if ! __cmd_exists bc; then
		__printf_color "Error: 'bc' command not found. Please install 'bc' to use this script." "1"
		exit 1
	fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
__show_help() {
	cat <<EOF
Usage: $APPNAME [OPTIONS] [COMMAND]

Commands:
  configure           Configure bandwidth for all services based on env vars
  status [service]    Show bandwidth status for a service
  calc <bandwidth>    Calculate rate/burst for given bandwidth (e.g., "1TB", "500GB")
  set <service> <bw>  Set bandwidth for specific service

Environment Variables:
  TOR_TOTAL_BANDWIDTH          Total bandwidth to divide among all 4 services
  TOR_SERVER_TOTAL_BANDWIDTH   Override for server service
  TOR_BRIDGE_TOTAL_BANDWIDTH   Override for bridge service
  TOR_RELAY_TOTAL_BANDWIDTH    Override for relay service
  TOR_EXIT_TOTAL_BANDWIDTH     Override for exit service
  TOR_BANDWIDTH_BURST_RATIO    Burst multiplier (default: 2)

Examples:
  $APPNAME configure
  $APPNAME calc 1TB
  $APPNAME calc 500GB
  $APPNAME set relay 250GB
  $APPNAME status server

  TOR_TOTAL_BANDWIDTH="2TB" $APPNAME configure
  TOR_EXIT_TOTAL_BANDWIDTH="500GB" $APPNAME configure

EOF
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Define Variables
DEFAULT_COLOR="254"
BANDWIDTH_USAGE_EXIT_STATUS=0
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Main application
__require_bc

case "${1:-}" in
configure | config)
	__configure_all_bandwidth
	;;
status)
	__get_accounting_status "${2:-server}"
	;;
calc | calculate)
	if [ -z "$2" ]; then
		__printf_color "Error: Please specify bandwidth (e.g., 1TB, 500GB)" "1"
		exit 1
	fi
	gb=$(__parse_bandwidth_to_gb "$2")
	if [ $? -eq 0 ]; then
		result=$(__calculate_bandwidth "$gb")
		accounting=$(echo "$result" | awk '{print $1}')
		rate=$(echo "$result" | awk '{print $2}')
		burst=$(echo "$result" | awk '{print $3}')
		echo "For $2 per month:"
		echo "  AccountingMax: $accounting GBytes"
		echo "  RelayBandwidthRate: $rate KB"
		echo "  RelayBandwidthBurst: $burst KB"
	fi
	;;
set)
	if [ -z "$2" ] || [ -z "$3" ]; then
		__printf_color "Error: Usage: $APPNAME set <service> <bandwidth>" "1"
		exit 1
	fi
	gb=$(__parse_bandwidth_to_gb "$3")
	if [ $? -eq 0 ]; then
		__configure_service "$2" "$gb"
	fi
	;;
help | --help | -h)
	__show_help
	;;
"")
	# If env vars are set, configure automatically
	if [ -n "$TOR_TOTAL_BANDWIDTH" ] || [ -n "$TOR_SERVER_TOTAL_BANDWIDTH" ] ||
		[ -n "$TOR_BRIDGE_TOTAL_BANDWIDTH" ] || [ -n "$TOR_RELAY_TOTAL_BANDWIDTH" ] ||
		[ -n "$TOR_EXIT_TOTAL_BANDWIDTH" ]; then
		__configure_all_bandwidth
	else
		__show_help
	fi
	;;
*)
	__printf_color "Error: Unknown command '$1'" "1"
	__show_help
	BANDWIDTH_USAGE_EXIT_STATUS=1
	;;
esac

# - - - - - - - - - - - - - - - - - - - - - - - - -
# End application
# - - - - - - - - - - - - - - - - - - - - - - - - -
# lets exit with code
# - - - - - - - - - - - - - - - - - - - - - - - - -
exit $BANDWIDTH_USAGE_EXIT_STATUS
# - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh
