#!/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
    log INFO "Configuring UFW..."
    ufw allow "${VPN_PORT}/tcp" >/dev/null 2>&1 || log WARNING "UFW allow failed"
    timeout 15 ufw reload >/dev/null 2>&1 || log WARNING "UFW reload timed out"
    return
  fi

  if command -v firewall-cmd >/dev/null 2>&1; then
    log INFO "Configuring firewalld..."
    firewall-cmd --permanent --add-port="${VPN_PORT}/tcp" >/dev/null 2>&1 || log WARNING "firewall-cmd add failed"
    timeout 15 firewall-cmd --reload >/dev/null 2>&1 || log WARNING "firewall-cmd reload timed out"
    return
  fi

  if command -v iptables >/dev/null 2>&1; then
    log INFO "Configuring iptables..."
    iptables -C INPUT -p tcp --dport "${VPN_PORT}" -j ACCEPT 2>/dev/null || \
      iptables -A INPUT -p tcp --dport "${VPN_PORT}" -j ACCEPT || log WARNING "iptables rule add failed"
    return
  fi

  if command -v nft >/dev/null 2>&1; then
    log INFO "Configuring nftables..."
    # 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 || log WARNING "nft rule add failed"
  fi
}

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

############################################
# HOSTNAME RESOLUTION (GLOBAL DNS SERVERS)
############################################
resolve_ipv4() {
  local host="$1"

  # Try with 1.1.1.1 (Cloudflare) first
  if command -v dig >/dev/null 2>&1; then
    local ip
    ip="$(dig +short +timeout=5 @"1.1.1.1" "$host" A 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1)"
    if [ -n "$ip" ]; then
      echo "$ip"
      return
    fi
  fi

  # Fallback to 8.8.8.8 (Google)
  if command -v dig >/dev/null 2>&1; then
    local ip
    ip="$(dig +short +timeout=5 @"8.8.8.8" "$host" A 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1)"
    if [ -n "$ip" ]; then
      echo "$ip"
      return
    fi
  fi

  # Fallback to nslookup with 1.1.1.1
  if command -v nslookup >/dev/null 2>&1; then
    local ip
    ip="$(nslookup "$host" 1.1.1.1 2>/dev/null | awk '/^Address: / {print $2; exit}')"
    if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
      echo "$ip"
      return
    fi
  fi

  # Last resort: nslookup with 8.8.8.8
  if command -v nslookup >/dev/null 2>&1; then
    local ip
    ip="$(nslookup "$host" 8.8.8.8 2>/dev/null | awk '/^Address: / {print $2; exit}')"
    if [ -n "$ip" ] && [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
      echo "$ip"
      return
    fi
  fi

  fail "Could not resolve hostname '$host' using global DNS servers."
}

############################################
# 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..."
  timeout 30 "$VPNCLIENT_BIN" start >/dev/null 2>&1 || fail "vpnclient failed to start within 30 seconds"

  log INFO "Waiting for vpnclient to be ready..."
  for _ in $(seq 1 20); do
    if timeout 5 "$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 within 20 seconds."
}

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.
  log INFO "Starting account import..."
  timeout 30 bash -c "
    {
      echo 'AccountImport'
      sleep 0.5
      echo '$VPN_ACCOUNT_FILE'
      sleep 0.5
      echo 'exit'
    } | '$VPNCMD_BIN' localhost /client >/dev/null 2>&1
  " || fail "Account import timed out after 30 seconds"

  sleep 1

  log INFO "Verifying account import..."
  timeout 10 "$VPNCMD_BIN" localhost /client /CMD AccountList 2>/dev/null | grep -q "$VPN_ACCOUNT" || \
    fail "Account import verification failed"
}

connect_vpn() {
  VPNCMD_BIN="$(command -v vpncmd)"
  log INFO "Connecting VPN..."

  "$VPNCMD_BIN" localhost /client /CMD AccountConnect "$VPN_ACCOUNT" >/dev/null 2>&1 || true

  # Wait for connection to establish
  log INFO "Waiting for VPN connection to establish..."
  for i in $(seq 1 15); do
    STATUS="$("$VPNCMD_BIN" localhost /client /CMD AccountStatusGet "$VPN_ACCOUNT" 2>/dev/null \
      | awk -F'|' '/Status/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2}')"

    if [ "$STATUS" = "Connected" ]; then
      log INFO "VPN connection established."
      return
    fi

    log INFO "VPN status: $STATUS (attempt $i/15)"
    sleep 2
  done

  log WARNING "VPN connection may not be fully established, proceeding anyway..."
}

############################################
# DHCP REQUEST (MULTI-CLIENT, AUTO-INSTALL, STEAMOS FIXES)
############################################
request_dhcp() {
    log INFO "Requesting DHCP lease on $VPN_INTERFACE..."

    # Detect SteamOS and handle special case
    if [ -f "/etc/os-release" ] && grep -qi "steamos" "/etc/os-release"; then
        log INFO "SteamOS detected - using specialized DHCP handling"

        # On SteamOS, ensure systemd-networkd is not managing the interface
        if systemctl is-active --quiet systemd-networkd; then
            log INFO "Disabling systemd-networkd for VPN interface..."
            networkctl down "$VPN_INTERFACE" 2>/dev/null || true
            sleep 1
        fi

        # SteamOS prefers dhcpcd with specific options
        if command -v dhcpcd >/dev/null 2>&1; then
            log INFO "Using dhcpcd (SteamOS optimized)"
            dhcpcd -k "$VPN_INTERFACE" >/dev/null 2>&1 || true
            sleep 2
            timeout 45 dhcpcd -t 30 -L "$VPN_INTERFACE" >/dev/null 2>&1 || true
            return
        fi
    fi

    # 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"
        timeout 30 dhclient -r "$VPN_INTERFACE" >/dev/null 2>&1 || true
        sleep 2
        timeout 30 dhclient -v "$VPN_INTERFACE" 2>&1 | head -20 >> "$LOG_FILE" || true
        return
    fi

    # dhcpcd (Arch / SteamOS / minimal installs)
    if command -v dhcpcd >/dev/null 2>&1; then
        log INFO "Using dhcpcd"
        timeout 15 dhcpcd -k "$VPN_INTERFACE" >/dev/null 2>&1 || true
        sleep 2
        timeout 30 dhcpcd -t 30 "$VPN_INTERFACE" 2>&1 | head -20 >> "$LOG_FILE" || 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 -t 5 2>&1 | head -20 >> "$LOG_FILE" || 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..."

    # First check if interface exists
    if ! ip link show "$VPN_INTERFACE" >/dev/null 2>&1; then
        fail "VPN interface $VPN_INTERFACE does not exist!"
    fi

    # Check if interface is up
    if ! ip link show "$VPN_INTERFACE" | grep -q "UP"; then
        log WARNING "VPN interface $VPN_INTERFACE is not UP, bringing it up..."
        ip link set "$VPN_INTERFACE" up || true
        sleep 2
    fi

    # On SteamOS, be more aggressive with timeout
    local max_attempts=30
    if [ -f "/etc/os-release" ] && grep -qi "steamos" "/etc/os-release"; then
        log INFO "SteamOS detected - using shorter IP wait timeout"
        max_attempts=15
    fi

    for i in $(seq 1 "$max_attempts"); 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
                log INFO "Ignoring link-local IP: $IP"
                sleep 1
                continue
            fi

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

        # Log progress every 5 seconds
        if [ $((i % 5)) -eq 0 ]; then
            log INFO "Still waiting for IP... ($i/$max_attempts)"
        fi

        sleep 1
    done

    # If we get here, DHCP failed - log interface state for debugging
    log ERROR "DHCP timeout - interface state:"
    ip addr show "$VPN_INTERFACE" >> "$LOG_FILE" 2>&1 || true
    ip route show >> "$LOG_FILE" 2>&1 || true

    fail "VPN did not receive valid DHCP address within ${max_attempts} seconds."
}

############################################
# 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 timeout 30 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" \
      >/dev/null 2>&1
    then
      log INFO "Game launched successfully via Wine."
      return
    fi
    log WARNING "Wine launch failed or timed out."
  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 timeout 30 sudo -u "$game_user" \
      DISPLAY="$display_env" \
      ${xdg_runtime:+XDG_RUNTIME_DIR="$xdg_runtime"} \
      steam -applaunch "$GAME_ID" -- \
      "/onlineUser:$online_user" \
      "/onlinePassword:$online_pass" \
      >/dev/null 2>&1
    then
      log INFO "Game launched successfully via Steam."
      return
    fi
    log WARNING "Steam launch with parameters failed or timed out."
  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..."
    timeout 10 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 ==="
