#!/usr/bin/env bash
# ACBMP Universal Linux Startup (SoftEther + DHCP + Game Launch)
# Works across: Ubuntu/Debian (older+newer), Fedora (dnf/yum), Arch/SteamOS, openSUSE
# Requires: run as root (sudo). Game launch runs as the original user.

set -euo pipefail
export PATH="/usr/sbin:/usr/bin:/sbin:/bin:$PATH"

############################################
# GLOBALS
############################################
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
LOG_FILE="${SCRIPT_DIR}/_ACBMP.sh.log"
CONFIG_FILE="${SCRIPT_DIR}/_ACBMP.ini"
HOSTS_FILE="/etc/hosts"

VPN_ACCOUNT="Animus Network"
VPN_NIC="vpn"
VPN_INTERFACE="vpn_${VPN_NIC}"
VPN_ACCOUNT_FILE="${SCRIPT_DIR}/Animus-Network.vpn"

GAME_ID="48190"
VPN_PORT="8443"

exec > >(tee -a "$LOG_FILE") 2>&1

############################################
# LOGGING / ERROR HANDLING
############################################
log() { printf "[%s] [%s] %s\n" "$(date '+%F %T')" "$1" "${*:2}"; }

fail() {
  log ERROR "$1"
  if [ -t 0 ]; then
    echo
    read -p "Press [Enter] to exit..."
  fi
  exit 1
}

trap 'fail "Unexpected error occurred."' ERR

############################################
# ROOT CHECK
############################################
[ "$(id -u)" -ne 0 ] && fail "Run as root (sudo)."

############################################
# DISTRO DETECTION (BACKWARD SAFE)
############################################
detect_distro() {
  if [ -f /etc/os-release ]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    DISTRO="${ID:-unknown}"
    DISTRO_LIKE="${ID_LIKE:-}"
  elif command -v lsb_release >/dev/null 2>&1; then
    DISTRO="$(lsb_release -si | tr '[:upper:]' '[:lower:]')"
    DISTRO_LIKE=""
  else
    DISTRO="unknown"
    DISTRO_LIKE=""
  fi

  log INFO "Detected distro: $DISTRO"
}

############################################
# ARCH DETECTION
############################################
detect_arch() {
  case "$(uname -m)" in
    x86_64|amd64) ARCH="x64" ;;
    i386|i686)   ARCH="x86" ;;
    aarch64|arm64) fail "Unsupported architecture for this setup: arm64" ;;
    *) fail "Unsupported architecture: $(uname -m)" ;;
  esac
  log INFO "Detected architecture: $ARCH"
}

############################################
# PACKAGE MANAGER HELPERS
############################################
pm_refresh() {
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update -y || true
  elif command -v apt >/dev/null 2>&1; then
    apt update || true
  elif command -v dnf >/dev/null 2>&1; then
    dnf -y makecache || true
  elif command -v yum >/dev/null 2>&1; then
    yum -y makecache || true
  elif command -v pacman >/dev/null 2>&1; then
    pacman -Sy --noconfirm || true
  elif command -v zypper >/dev/null 2>&1; then
    zypper --gpg-auto-import-keys refresh || true
  fi
}

pm_install() {
  if command -v apt-get >/dev/null 2>&1; then
    DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"
  elif command -v apt >/dev/null 2>&1; then
    apt install -y "$@"
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y "$@"
  elif command -v yum >/dev/null 2>&1; then
    yum install -y "$@"
  elif command -v pacman >/dev/null 2>&1; then
    pacman -S --needed --noconfirm "$@"
  elif command -v zypper >/dev/null 2>&1; then
    zypper install -y "$@"
  else
    fail "No supported package manager found."
  fi
}

install_dependencies() {
  log INFO "Installing build/runtime dependencies..."

  pm_refresh

  # Base build tooling (names differ; install best-effort per family)
  case "$DISTRO" in
    ubuntu|debian|linuxmint|pop)
      pm_install build-essential gcc make cmake curl libssl-dev libreadline-dev zlib1g-dev || true
      # DHCP clients (older minimal installs might miss both)
      pm_install isc-dhcp-client dhcpcd5 || true
      ;;
    fedora)
      pm_install gcc make cmake curl openssl-devel readline-devel zlib-devel || true
      pm_install dhcp-client dhcpcd || true
      ;;
    rhel|centos|rocky|almalinux)
      pm_install gcc make cmake curl openssl-devel readline-devel zlib-devel || true
      pm_install dhcp-client dhcpcd || true
      ;;
    arch|manjaro|steamos)
      pm_install base-devel cmake curl openssl readline zlib || true
      pm_install dhclient dhcpcd || true
      ;;
    opensuse*|suse|sled|sles)
      pm_install gcc make cmake curl libopenssl-devel readline-devel zlib-devel || true
      pm_install dhcp-client dhcpcd || true
      ;;
    *)
      # Fallback: try common names, ignore failures
      pm_install gcc make cmake curl || true
      pm_install libssl-dev openssl-devel || true
      pm_install libreadline-dev readline-devel || true
      pm_install zlib1g-dev zlib-devel || true
      pm_install dhclient dhcpcd isc-dhcp-client dhcp-client || true
      ;;
  esac
}

############################################
# SOFTETHER INSTALL (BUILD FROM SOURCE IF MISSING)
############################################
install_softether_if_missing() {
  if command -v vpnclient >/dev/null 2>&1 && command -v vpncmd >/dev/null 2>&1; then
    log INFO "SoftEther already installed."
    return
  fi

  log INFO "SoftEther not found. Building from source."
  install_dependencies

  command -v curl >/dev/null 2>&1 || fail "curl is required but not installed."

  local tmp="/tmp/softether_build"
  rm -rf "$tmp"
  mkdir -p "$tmp"
  cd "$tmp"

  local url="https://github.com/SoftEtherVPN/SoftEtherVPN_Stable/archive/refs/heads/master.tar.gz"

  log INFO "Downloading SoftEther..."
  curl -fsSL "$url" -o softether.tar.gz || fail "SoftEther download failed."

  log INFO "Extracting..."
  tar -xzf softether.tar.gz || fail "Extraction failed."

  cd SoftEtherVPN_Stable-* || fail "Source folder missing after extraction."

  log INFO "Configuring..."
  ./configure || fail "Configure failed."

  log INFO "Compiling..."
  make -j"$(nproc)" || fail "Compilation failed."

  log INFO "Installing..."
  make install || fail "Installation failed."

  cd "$SCRIPT_DIR"
  log INFO "SoftEther installed successfully."
}

############################################
# FIREWALL (UFW / firewalld / iptables / nft)
############################################
configure_firewall() {
  log INFO "Ensuring TCP ${VPN_PORT} is open (best-effort)."

  if command -v ufw >/dev/null 2>&1; then
    ufw allow "${VPN_PORT}/tcp" >/dev/null 2>&1 || true
    ufw reload >/dev/null 2>&1 || true
    return
  fi

  if command -v firewall-cmd >/dev/null 2>&1; then
    firewall-cmd --permanent --add-port="${VPN_PORT}/tcp" >/dev/null 2>&1 || true
    firewall-cmd --reload >/dev/null 2>&1 || true
    return
  fi

  if command -v iptables >/dev/null 2>&1; then
    iptables -C INPUT -p tcp --dport "${VPN_PORT}" -j ACCEPT 2>/dev/null || \
      iptables -A INPUT -p tcp --dport "${VPN_PORT}" -j ACCEPT || true
    return
  fi

  if command -v nft >/dev/null 2>&1; then
    # Best-effort: many distros have inet filter table/chains by default; ignore failures.
    nft add rule inet filter input tcp dport "${VPN_PORT}" accept 2>/dev/null || true
  fi
}

############################################
# CONFIG PARSING
############################################
cfg_get() {
    grep -E "^$1=" "$CONFIG_FILE" \
    | head -n1 \
    | cut -d'=' -f2- \
    | tr -d '\r'
}

############################################
# HOSTNAME RESOLUTION (BACKWARD SAFE)
############################################
resolve_ipv4() {
  local host="$1"

  if command -v getent >/dev/null 2>&1; then
    getent ahostsv4 "$host" | awk '{print $1; exit}'
    return
  fi

  if command -v host >/dev/null 2>&1; then
    host "$host" | awk '/has address/ {print $4; exit}'
    return
  fi

  if command -v nslookup >/dev/null 2>&1; then
    nslookup "$host" 2>/dev/null | awk '/^Address: / {print $2; exit}'
    return
  fi

  fail "No resolver tool found (need getent/host/nslookup)."
}

############################################
# SERVER REDIRECTION BY UPDATING HOSTS
############################################
update_hosts() {

    [ ! -f "$CONFIG_FILE" ] && fail "_ACBMP.ini missing."

    local server_ip server_host

    server_ip="$(cfg_get ServerIP)"
    server_host="$(cfg_get ServerHostname)"

    if [ -z "$server_ip" ]; then
        [ -z "$server_host" ] && fail "ServerIP or ServerHostname required."
        server_ip="$(resolve_ipv4 "$server_host")"
    fi

    [ -z "$server_ip" ] && fail "Could not determine server IP."

    log INFO "Using server IP: $server_ip"

    cp "$HOSTS_FILE" "${HOSTS_FILE}.bak" 2>/dev/null || true
    sed -i '/onlineconfigservice\.ubi\.com/d' "$HOSTS_FILE"

    printf "%s\t%s\n" "$server_ip" "onlineconfigservice.ubi.com" >> "$HOSTS_FILE"
}

############################################
# SOFTETHER CONTROL
############################################
start_client() {
  VPNCLIENT_BIN="$(command -v vpnclient || true)"
  VPNCMD_BIN="$(command -v vpncmd || true)"

  [ -z "${VPNCLIENT_BIN:-}" ] && fail "vpnclient not found."
  [ -z "${VPNCMD_BIN:-}" ] && fail "vpncmd not found."

  log INFO "Starting vpnclient..."
  "$VPNCLIENT_BIN" start >/dev/null 2>&1 || true

  for _ in $(seq 1 20); do
    if "$VPNCMD_BIN" localhost /client /CMD AccountList >/dev/null 2>&1; then
      log INFO "vpnclient ready."
      return
    fi
    sleep 1
  done

  fail "vpnclient failed to initialize."
}

ensure_nic() {
  VPNCMD_BIN="$(command -v vpncmd)"

  log INFO "Resetting VPN NIC..."

  "$VPNCMD_BIN" localhost /client /CMD NicDisable "$VPN_NIC" >/dev/null 2>&1 || true
  "$VPNCMD_BIN" localhost /client /CMD NicDelete "$VPN_NIC"  >/dev/null 2>&1 || true

  sleep 1

  log INFO "Creating VPN NIC: $VPN_NIC"
  "$VPNCMD_BIN" localhost /client /CMD NicCreate "$VPN_NIC" >/dev/null 2>&1 || true

  log INFO "Enabling VPN NIC..."
  "$VPNCMD_BIN" localhost /client /CMD NicEnable "$VPN_NIC" >/dev/null 2>&1 || true

  # Verify NIC exists now; if not, fail
  if ! "$VPNCMD_BIN" localhost /client /CMD NicList 2>/dev/null | grep -qiE "(^|[[:space:]])${VPN_NIC}($|[[:space:]])"; then
    # Some versions format NicList oddly; as last resort, attempt enable and trust it:
    log WARNING "Could not verify NIC via NicList output; proceeding anyway."
  fi
}

cleanup_accounts() {
  VPNCMD_BIN="$(command -v vpncmd)"
  log INFO "Cleaning up existing VPN accounts matching: $VPN_ACCOUNT"

  "$VPNCMD_BIN" localhost /client /CMD AccountList 2>/dev/null \
    | awk -F'|' '/VPN Connection Setting Name/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}' \
    | while read -r acc; do
        [ -z "$acc" ] && continue
        if echo "$acc" | grep -qi "^${VPN_ACCOUNT}"; then
          "$VPNCMD_BIN" localhost /client /CMD AccountDisconnect "$acc" >/dev/null 2>&1 || true
          "$VPNCMD_BIN" localhost /client /CMD AccountDelete "$acc" >/dev/null 2>&1 || true
        fi
      done
}

ensure_account() {
  cleanup_accounts
  [ ! -f "$VPN_ACCOUNT_FILE" ] && fail "VPN config file missing: $VPN_ACCOUNT_FILE"

  VPNCMD_BIN="$(command -v vpncmd)"
  log INFO "Importing VPN account..."

  # SoftEther CLI is inconsistent; this interactive-style import works for 4.44 Linux and older.
  {
    echo "AccountImport"
    sleep 0.5
    echo "$VPN_ACCOUNT_FILE"
    sleep 0.5
    echo "exit"
  } | "$VPNCMD_BIN" localhost /client >/dev/null || true

  sleep 1

  if ! "$VPNCMD_BIN" localhost /client /CMD AccountList 2>/dev/null | grep -q "$VPN_ACCOUNT"; then
    fail "Account import failed."
  fi
}

connect_vpn() {
  VPNCMD_BIN="$(command -v vpncmd)"
  log INFO "Connecting VPN..."
  "$VPNCMD_BIN" localhost /client /CMD AccountConnect "$VPN_ACCOUNT" >/dev/null 2>&1 || true
}

############################################
# DHCP REQUEST (MULTI-CLIENT, AUTO-INSTALL)
############################################
request_dhcp() {

    log INFO "Requesting DHCP lease on $VPN_INTERFACE..."

    # Helper: install dhcpcd if on Arch/SteamOS and missing
    if ! command -v dhclient >/dev/null 2>&1 \
       && ! command -v dhcpcd >/dev/null 2>&1 \
       && ! command -v udhcpc >/dev/null 2>&1; then

        log WARNING "No DHCP client found. Attempting to install dhcpcd..."

        if command -v pacman >/dev/null 2>&1; then
            pacman -S --needed --noconfirm dhcpcd || fail "Failed to install dhcpcd"
            log INFO "dhcpcd installed successfully."
        else
            fail "No supported DHCP client found (dhclient, dhcpcd, or udhcpc) and cannot install automatically."
        fi
    fi

    # dhclient (Debian/Ubuntu/RHEL most common)
    if command -v dhclient >/dev/null 2>&1; then
        log INFO "Using dhclient"
        dhclient -r "$VPN_INTERFACE" >/dev/null 2>&1 || true
        sleep 1
        timeout 30 dhclient "$VPN_INTERFACE" >/dev/null 2>&1 || true
        return
    fi

    # dhcpcd (Arch / SteamOS / minimal installs)
    if command -v dhcpcd >/dev/null 2>&1; then
        log INFO "Using dhcpcd"
        dhcpcd -k "$VPN_INTERFACE" >/dev/null 2>&1 || true
        sleep 1
        timeout 30 dhcpcd "$VPN_INTERFACE" >/dev/null 2>&1 || true
        return
    fi

    # udhcpc (BusyBox / very minimal systems)
    if command -v udhcpc >/dev/null 2>&1; then
        log INFO "Using udhcpc"
        timeout 30 udhcpc -i "$VPN_INTERFACE" -n -q >/dev/null 2>&1 || true
        return
    fi

    fail "No supported DHCP client found (dhclient, dhcpcd, or udhcpc)."
}

wait_for_ip() {

    log INFO "Waiting for DHCP IP on $VPN_INTERFACE..."

    for i in $(seq 1 30); do
        IP=$(ip -4 addr show "$VPN_INTERFACE" 2>/dev/null | awk '/inet / {print $2}' | head -n1)

        if [ -n "$IP" ]; then
            if echo "$IP" | grep -q "^169\.254\."; then
                # silently ignore temporary link-local
                sleep 1
                continue
            fi

            log INFO "Valid VPN IP acquired: $IP"
            return
        fi

        sleep 1
    done

    fail "VPN did not receive valid DHCP address."
}

############################################
# GAME LAUNCH (ALL FALLBACKS)
############################################
launch_game() {
  [ ! -f "$SCRIPT_DIR/ACBSP.exe" ] && fail "ACBSP.exe not found in $SCRIPT_DIR"

  local online_user online_pass
  online_user="$(cfg_get onlineUser)"
  online_pass="$(cfg_get onlinePassword)"

  [ -z "${online_user:-}" ] && fail "onlineUser missing in _ACBMP.ini"
  [ -z "${online_pass:-}" ] && fail "onlinePassword missing in _ACBMP.ini"

  local game_user user_uid display_env xdg_runtime
  game_user="${SUDO_USER:-$USER}"
  user_uid="$(id -u "$game_user")"
  display_env="${DISPLAY:-:0}"
  xdg_runtime="/run/user/$user_uid"

  log INFO "Launching game as user: $game_user"
  log INFO "Using DISPLAY=$display_env"

  # Ensure XDG_RUNTIME_DIR exists for the target user (older distros / minimal installs)
  if [ ! -d "$xdg_runtime" ]; then
    log WARNING "XDG_RUNTIME_DIR $xdg_runtime not found. Proceeding without it."
    xdg_runtime=""
  fi

  ############################################
  # Attempt 1: Wine (as normal user)
  ############################################
  if command -v wine >/dev/null 2>&1; then
    log INFO "Attempting launch via Wine..."
    if sudo -u "$game_user" \
      DISPLAY="$display_env" \
      ${xdg_runtime:+XDG_RUNTIME_DIR="$xdg_runtime"} \
      WINEDEBUG=-all \
      wine "$SCRIPT_DIR/ACBSP.exe" \
      "/onlineUser:$online_user" \
      "/onlinePassword:$online_pass"
    then
      log INFO "Game launched successfully via Wine."
      return
    fi
    log WARNING "Wine launch failed."
  else
    log WARNING "Wine not installed."
  fi

  ############################################
  # Attempt 2: Steam with params
  ############################################
  if command -v steam >/dev/null 2>&1; then
    log INFO "Attempting launch via Steam (with parameters)..."
    if sudo -u "$game_user" \
      DISPLAY="$display_env" \
      ${xdg_runtime:+XDG_RUNTIME_DIR="$xdg_runtime"} \
      steam -applaunch "$GAME_ID" -- \
      "/onlineUser:$online_user" \
      "/onlinePassword:$online_pass"
    then
      log INFO "Game launched successfully via Steam."
      return
    fi
    log WARNING "Steam launch with parameters failed."
  else
    log WARNING "Steam not installed."
  fi

  ############################################
  # Attempt 3: Steam without params (Proton fallback)
  ############################################
  if command -v steam >/dev/null 2>&1; then
    log INFO "Attempting Steam launch without parameters..."
    sudo -u "$game_user" \
      DISPLAY="$display_env" \
      ${xdg_runtime:+XDG_RUNTIME_DIR="$xdg_runtime"} \
      steam -applaunch "$GAME_ID" >/dev/null 2>&1 || true
    log INFO "Steam fallback triggered."
    return
  fi

  fail "All launch methods failed (Wine + Steam)."
}

############################################
# MAIN
############################################
log INFO "=== ACBMP Universal Launcher ==="

detect_distro
detect_arch
install_softether_if_missing
configure_firewall
update_hosts

start_client
ensure_nic
ensure_account
connect_vpn

sleep 2
request_dhcp
wait_for_ip
launch_game

log INFO "=== Done ==="

