#!/bin/bash
# shellcheck disable=SC2016,SC2116,SC2001
set -eaBo pipefail
shopt -s extglob
shopt -s expand_aliases
[[ ${debug:="no"} =~ ^(yes|true|ok)$ ]] && set -x

function main() {
  local os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  local current=$(cd "$(dirname "$0")" && pwd)
  local home="$HOME"
  local project=$(basename "$current")
  local script="$(basename "$0")"
  local opts=$1

  # global vars
  export HOMEBREW_NO_AUTO_UPDATE=1
  export HOMEBREW_NO_INSTALL_FROM_API=1

  local project="cloudnet"
  local base_dir="/usr/local/cloudnet"
  local bin_dir="$base_dir/bin"
  local bin_cli="$bin_dir/$project"
  local bin_srv="$bin_dir/${project}d"
  local interface="utun"
  local servName="CloudNetService"
  local plistName="world.cloudnet.client.cloudnetd"
  local plist="/Library/LaunchDaemons/$plistName.plist"
  local ctrl_server="https://cp.cloudnet.world"
  local auth_key="your_pre_auth_key"

  ## help
  { [[ $opts =~ ^(-h|--help|help)$ ]] || [[ $# == 0 ]]; } && usage
  ## options
  [[ -n $opts ]] && ("$@" && return 0)
}

function usage() {
  local fncs=$(declare -F -p | cut -d " " -f 3 | grep -vE "${FUNCNAME[0]}|main|which|^_") # Get function list
  local help=$(echo $fncs | sed 's/ / | /g')
  cat <<HELP
Usage: $help
  $script start --accept-dns=false --accept-routes=false --reset --auth-key=$auth_key
HELP
  [[ ${debug:="no"} =~ ^(yes|true|ok)$ ]] && echo "-->> BashVersion: [$BASH_VERSION]"
  return $LINENO
}

function version() {
  echo '==>> Cli version:'
  $bin_cli version
  echo '==>> Daemon version:'
  $bin_srv -version
}

function vercheck() {
  local runVersion="$($bin_srv -version | grep -iE "commit:" | awk '$0=$(NF-0)')"
  local curVersion="$($current/cloudnetd -version | grep -iE "commit:" | awk '$0=$(NF-0)')"

  echo "==>> Current [$curVersion] | Running [$runVersion]"
  if [[ $runVersion != "$curVersion" ]]; then
    echo "==>> Upgrade version: $curVersion"
    return 1
  else
    echo "==>> No need upgrade"
    return 0
  fi
}

function _check_serv() {
  local service="${1:-"$plistName"}"

  if sudo launchctl list | grep -iE "$service" >/dev/null 2>&1; then
    return 0
  else
    return 1
  fi
}

function _check_serv_running() {
  local service="${1:-"$plistName"}"

  if _check_serv "$service"; then
    local pid=$(sudo launchctl list | grep -iE "$service" | awk '$0=$1')
    if [[ $pid != "-" ]]; then
      return 0
    else
      return 1
    fi
  else
    return 1
  fi
}

function start() {
  local args="$(shift 0 && printf "%s" "$*")"

  if _check_serv_running $plistName; then ## if return is 0
    echo -e "==>> $servName is Running"
  else ## if return is 1
    echo -e "==>> Starting $servName..."
    sudo launchctl load $plist
  fi

  until _check_serv_running $plistName; do ## if Check return 1 then do it
    echo -e "==>> Waiting $servName for running..."
    sleep 3
  done

  echo -e "==>> Started $servName!"
  sudo launchctl list | grep -iE $plistName

  if [[ -n $args ]]; then
    echo -e "==>> Register the current host: $(hostname -s)"
    if $bin_cli up $args; then
      echo -e "==>> Registration success"
    else
      echo -e "==>> Registration failed"
      return 1
    fi
  fi
}

function stop() {
  if _check_serv_running $plistName; then
    echo -e "==>> Stopping $servName..."
    while _check_serv_running $plistName; do ## if Check return 0 then do it
      sudo launchctl kill -9 system/$plistName
      sudo launchctl list | grep -iE $plistName
      sudo launchctl unload $plist
    done
    echo -e "==>> Stopped $servName!"
  else
    echo -e "==>> Stopped $servName!"
  fi
}

function ping() {
  echo -e "==>> Running ping test"
  $(which ping) -c 5 "$@"
  echo -e "==>> Running cloudnet test"
  sudo $bin_cli ping "$@"
}

function netcheck() {
  echo -e "==>> Running network check"
  $bin_cli netcheck
}

function status() {
  echo -e "==>> Service status"
  sudo launchctl list | grep -iE $plistName

  if ! _status_ipr; then
    _status_netstat
  fi
  _status_lsof
  _status_ps

  echo -e "==>> Nodes status"
  $bin_cli status
}

function _status_ps() {
  echo "==>> Process status"
  # shellcheck disable=SC2009
  ps aux | grep -iv grep | grep -iE "bin/cloudnetd$"
}

function _status_ipr() {
  if [[ -x "$(command -v ip)" ]]; then
    echo -e "==>> Route status($interface)"
    ip r | grep -v grep | grep -ie $interface
  fi
}

function _status_netstat() {
  if [[ -x "$(command -v netstat)" ]]; then
    echo -e "==>> Route status($interface)"
    netstat -nr | grep -v '::' | grep -ie $interface
  fi
}

function _status_lsof() {
  if [[ -x "$(command -v lsof)" ]]; then
    echo "==>> Connection status"
    sudo -E lsof -Pni | grep -ie cloudnetd
  fi
}

function restart() {
  stop
  sudo launchctl unload $plist
  sudo launchctl load $plist
}

function enable() {
  sudo launchctl load $plist
}

function disable() {
  sudo launchctl unload $plist
}

function reset() {
  $bin_cli down     ## 退出登录,需要重新up才能使用
  $bin_srv -cleanup ## 清理目录,包含用户登录状态
  [[ -d /Library/Cloudnet/ ]] && sudo rm -rf /Library/Cloudnet/
  [[ -d $HOME/.local/share/cloudnet ]] && sudo -E rm -rf $HOME/.local/share/cloudnet
  return 0
}

function socks() {
  local opts="${1:-""}"
  local port="${2:-"8891"}"
  local filepath="${3:-"$base_dir/$plistName.plist"}"

  if [[ $opts =~ (on) ]]; then
    if ! /usr/libexec/PlistBuddy -c "Print :ProgramArguments" $filepath | grep -q "socks5-server"; then
      # 添加 socks5-server 和 0.0.0.0:$port 到 ProgramArguments 数组
      /usr/libexec/PlistBuddy -c "Add :ProgramArguments: string --socks5-server" $filepath
      /usr/libexec/PlistBuddy -c "Add :ProgramArguments: string 0.0.0.0:$port" $filepath
      if [[ $debug == 'no' ]]; then
        restart
        sleep 3
        _status_lsof
      fi
    fi
  elif [[ $opts =~ (off) ]]; then
    if /usr/libexec/PlistBuddy -c "Print :ProgramArguments" $filepath | grep -q "socks5-server"; then
      # 删除 socks5-server 和 0.0.0.0:$port
      /usr/libexec/PlistBuddy -c "Delete :ProgramArguments:2" $filepath
      /usr/libexec/PlistBuddy -c "Delete :ProgramArguments:1" $filepath
      if [[ $debug == 'no' ]]; then
        restart
        sleep 3
        _status_lsof
      fi
    fi
  else
    echo "-->> ${FUNCNAME[0]}: on|off port(8891)"
    return
  fi
  echo "==>> socks is $opts"
}

function cli() {
  shift 1
  $bin_cli "$@"
}

function logs() {
  local opts="${1:-"show"}"

  if [[ $opts =~ (show) ]]; then
    echo "==>> logs stream"
    tail -f -n 1000 /usr/local/var/log/cloudnetd.*
  elif [[ $opts =~ (clean) ]]; then
    echo "==>> logs cleanup"
    sudo rm -rf /usr/local/var/log/cloudnetd.*
  fi
}

function upgrade() {
  brew upgrade --force --cask cloudnet
}

function install() {
  local opts="${1:-""}"
  local args="$(shift 1 && printf "%s" "$*")"

  stop

  echo "==>> Install/Upgrade service"
  # no need install brew
  sudo mkdir -p $bin_dir
  sudo ditto $current/cloudnetd.plist $plist
  sudo ditto $current/cloudnetd $bin_srv
  sudo ditto $current/cloudnet $bin_cli
  sudo ditto $current/cnet $bin_dir/cnet

  sudo chmod 0755 $bin_dir/cnet
  sudo chown root:admin $plist

  sudo ln -sf $bin_dir/cnet /usr/local/bin/cnet
  sudo ln -sf $bin_dir/cloudnet /usr/local/bin/cloudnet

  start $args
}

function uninstall() {
  stop

  local cleans=(
    "/usr/local/cloudnet"
    "/usr/local/Cellar/cloudnet"
    "/usr/local/var/log/cloudnetd.log"
    "/Library/LaunchDaemons/$plistName.plist"
    "/Library/Cloudnet" ## status database files
    "$HOME/.local/share/cloudnet"
    "/var/run/cloudnetd.socket"
  )
  echo "==>> Remove service"
  for clean in "${cleans[@]}"; do
    if [[ -d "$clean" || -f "$clean" ]]; then
      echo "-->> Remove: [$clean]"
      sudo rm -rf "${clean:?}"
    fi
  done

  local services=(
    "homebrew.mxcl.cloudnet"
    "$plistName"
  )
  echo "==>> Remove launchctl service"
  for service in "${services[@]}"; do
    if _check_serv "$service"; then
      echo "-->> Remove: [$service]"
      sudo launchctl remove "$service"
    fi
  done
}

## call main
main "$@"
