#!/usr/bin/env sh
set -eu

APP_NAME="SMS Gateway"
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
PACKAGE_URL="${SMS_GATEWAY_PACKAGE_URL:-__SMS_GATEWAY_DEPLOY_PACKAGE_URL__}"
INSTALL_DIR="${SMS_GATEWAY_INSTALL_DIR:-/opt/sms-gateway}"
WORK_DIR=""
COMPOSE_CMD=""
OS_ID=""
OS_ID_LIKE=""
OS_VERSION_ID=""
OS_VERSION_MAJOR=""
OS_VERSION_CODENAME=""

log() {
  printf '%s\n' "$*"
}

fail() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

run_as_root() {
  if [ "$(id -u)" -eq 0 ]; then
    "$@"
  elif command -v sudo >/dev/null 2>&1; then
    sudo "$@"
  else
    fail "root privileges are required. Re-run with sudo."
  fi
}

run_root_sh() {
  script="$1"
  if [ "$(id -u)" -eq 0 ]; then
    sh -c "$script"
  elif command -v sudo >/dev/null 2>&1; then
    sudo sh -c "$script"
  else
    fail "root privileges are required. Re-run with sudo."
  fi
}

cleanup() {
  if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
    run_as_root rm -rf "$WORK_DIR"
  fi
}

trap cleanup EXIT INT TERM

require_package_url() {
  LOCAL_PACKAGE_FILE="${SCRIPT_DIR}/sms-gateway-deploy.tar.gz"

  if [ -z "$PACKAGE_URL" ] || [ "$PACKAGE_URL" = "__SMS_GATEWAY_DEPLOY_PACKAGE_URL__" ]; then
    if [ -f "$LOCAL_PACKAGE_FILE" ]; then
      PACKAGE_URL="file://${LOCAL_PACKAGE_FILE}"
      if [ -f "${LOCAL_PACKAGE_FILE}.sha256" ] && [ -z "${SMS_GATEWAY_SHA256_URL:-}" ]; then
        SMS_GATEWAY_SHA256_URL="file://${LOCAL_PACKAGE_FILE}.sha256"
        export SMS_GATEWAY_SHA256_URL
      fi
      log "Using local deploy package: ${LOCAL_PACKAGE_FILE}"
      return
    fi

    fail "PACKAGE_URL is still a placeholder. Put sms-gateway-deploy.tar.gz next to bootstrap-install.sh or run with SMS_GATEWAY_PACKAGE_URL=https://your-server/sms-gateway-deploy.tar.gz"
  fi
}

load_os_release() {
  if [ ! -r /etc/os-release ]; then
    fail "/etc/os-release is missing; cannot detect a supported Linux distribution."
  fi

  # shellcheck disable=SC1091
  . /etc/os-release

  OS_ID="${ID:-}"
  OS_ID_LIKE="${ID_LIKE:-}"
  OS_VERSION_ID="${VERSION_ID:-}"
  OS_VERSION_MAJOR=$(printf '%s' "$OS_VERSION_ID" | cut -d. -f1)
  OS_VERSION_CODENAME="${VERSION_CODENAME:-}"
}

detect_compose_cmd() {
  if command -v docker >/dev/null 2>&1 && run_as_root docker compose version >/dev/null 2>&1; then
    printf '%s\n' "docker compose"
    return 0
  fi

  if command -v docker-compose >/dev/null 2>&1 && run_as_root docker-compose version >/dev/null 2>&1; then
    printf '%s\n' "docker-compose"
    return 0
  fi

  return 1
}

compose_cmd() {
  if [ -z "$COMPOSE_CMD" ]; then
    COMPOSE_CMD=$(detect_compose_cmd) || fail "Docker Compose is missing, please install docker compose or docker-compose and retry."
  fi

  # shellcheck disable=SC2086
  run_as_root $COMPOSE_CMD "$@"
}

ensure_docker_running() {
  if ! command -v docker >/dev/null 2>&1; then
    return 1
  fi

  if run_as_root docker info >/dev/null 2>&1; then
    return
  fi

  if command -v systemctl >/dev/null 2>&1; then
    run_as_root systemctl enable --now docker >/dev/null 2>&1 || true
  elif command -v rc-service >/dev/null 2>&1; then
    run_as_root rc-update add docker default >/dev/null 2>&1 || true
    run_as_root rc-service docker start >/dev/null 2>&1 || true
  elif command -v service >/dev/null 2>&1; then
    run_as_root service docker start >/dev/null 2>&1 || true
  fi

  run_as_root docker info >/dev/null 2>&1
}

install_docker_debian_like() {
  DOCKER_APT_DIST="$1"

  case "$OS_VERSION_MAJOR" in
    11|12|13|22|24|25|26)
      ;;
    *)
      fail "Unsupported ${OS_ID} version '${OS_VERSION_ID}'. Supported Debian versions: Debian 11, Debian 12, Debian 13. Supported Ubuntu versions: Ubuntu 22.04, Ubuntu 24.04, Ubuntu 25.10, Ubuntu 26.04."
      ;;
  esac

  if [ -z "$OS_VERSION_CODENAME" ]; then
    fail "VERSION_CODENAME is missing in /etc/os-release; cannot configure Docker apt repository."
  fi

  log "Installing Docker Engine for ${OS_ID} ${OS_VERSION_ID} (${OS_VERSION_CODENAME})..."

  run_as_root apt-get update
  run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl gnupg tar

  for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
    run_as_root apt-get remove -y "$pkg" >/dev/null 2>&1 || true
  done

  configure_docker_apt_repo "$DOCKER_APT_DIST"
  run_as_root apt-get update
  run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
}

configure_docker_apt_repo() {
  DOCKER_APT_DIST="$1"

  run_as_root install -m 0755 -d /etc/apt/keyrings
  run_root_sh "curl -fsSL https://download.docker.com/linux/${DOCKER_APT_DIST}/gpg -o /etc/apt/keyrings/docker.asc"
  run_as_root chmod a+r /etc/apt/keyrings/docker.asc

  ARCH=$(dpkg --print-architecture)
  run_root_sh "cat > /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/${DOCKER_APT_DIST}
Suites: ${OS_VERSION_CODENAME}
Components: stable
Architectures: ${ARCH}
Signed-By: /etc/apt/keyrings/docker.asc
EOF"
}

install_docker_rhel_like() {
  DOCKER_RPM_DIST="$1"

  case "$OS_ID:$OS_VERSION_MAJOR" in
    rhel:8|rhel:9|rhel:10|rocky:8|rocky:9|rocky:10|almalinux:8|almalinux:9|almalinux:10|ol:8|ol:9|ol:10|centos:9|centos:10|fedora:43|fedora:44)
      ;;
    *)
      fail "Unsupported ${OS_ID} version '${OS_VERSION_ID}' for Docker rpm repository installation."
      ;;
  esac

  log "Installing Docker Engine for ${OS_ID} ${OS_VERSION_ID}..."

  if command -v dnf >/dev/null 2>&1; then
    run_as_root dnf -y install dnf-plugins-core ca-certificates
    command -v curl >/dev/null 2>&1 || run_as_root dnf -y install curl
    configure_docker_rpm_repo "$DOCKER_RPM_DIST" dnf
    run_as_root dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  elif command -v yum >/dev/null 2>&1; then
    run_as_root yum -y install yum-utils ca-certificates
    command -v curl >/dev/null 2>&1 || run_as_root yum -y install curl
    configure_docker_rpm_repo "$DOCKER_RPM_DIST" yum
    run_as_root yum -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  else
    fail "dnf or yum is required to install Docker on ${OS_ID}."
  fi
}

configure_docker_rpm_repo() {
  DOCKER_RPM_DIST="$1"
  RPM_MANAGER="$2"
  REPO_URL="https://download.docker.com/linux/${DOCKER_RPM_DIST}/docker-ce.repo"

  if [ "$RPM_MANAGER" = "dnf" ]; then
    run_as_root dnf config-manager --add-repo "$REPO_URL"
  else
    run_as_root yum-config-manager --add-repo "$REPO_URL"
  fi
}

install_docker_alpine() {
  case "$OS_VERSION_MAJOR" in
    3)
      ;;
    *)
      fail "Unsupported Alpine version '${OS_VERSION_ID}'. Supported Alpine versions: 3.21, 3.22, 3.23, 3.24."
      ;;
  esac

  log "Installing Docker Engine for Alpine ${OS_VERSION_ID}..."
  run_as_root apk update
  run_as_root apk add --no-cache docker docker-cli-compose curl tar openssl
  run_as_root rc-update add docker default >/dev/null 2>&1 || true
  run_as_root rc-service docker start >/dev/null 2>&1 || true
}

install_compose_for_detected_os() {
  case "$OS_ID" in
    debian|ubuntu)
      configure_docker_apt_repo "$OS_ID"
      run_as_root apt-get update
      run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin
      ;;
    rhel|rocky|almalinux|ol|centos|fedora)
      if command -v dnf >/dev/null 2>&1; then
        run_as_root dnf -y install dnf-plugins-core ca-certificates
        command -v curl >/dev/null 2>&1 || run_as_root dnf -y install curl
        case "$OS_ID" in
          centos) configure_docker_rpm_repo centos dnf ;;
          fedora) configure_docker_rpm_repo fedora dnf ;;
          *) configure_docker_rpm_repo rhel dnf ;;
        esac
        run_as_root dnf -y install docker-compose-plugin
      elif command -v yum >/dev/null 2>&1; then
        run_as_root yum -y install yum-utils ca-certificates
        command -v curl >/dev/null 2>&1 || run_as_root yum -y install curl
        case "$OS_ID" in
          centos) configure_docker_rpm_repo centos yum ;;
          fedora) configure_docker_rpm_repo fedora yum ;;
          *) configure_docker_rpm_repo rhel yum ;;
        esac
        run_as_root yum -y install docker-compose-plugin
      else
        fail "dnf or yum is required to install Docker Compose on ${OS_ID}."
      fi
      ;;
    alpine)
      run_as_root apk update
      run_as_root apk add --no-cache docker-cli-compose
      ;;
    *)
      case " $OS_ID_LIKE " in
        *" debian "*)
          configure_docker_apt_repo debian
          run_as_root apt-get update
          run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin
          ;;
        *" rhel "*|*" fedora "*)
          if command -v dnf >/dev/null 2>&1; then
            run_as_root dnf -y install dnf-plugins-core ca-certificates
            command -v curl >/dev/null 2>&1 || run_as_root dnf -y install curl
            configure_docker_rpm_repo rhel dnf
            run_as_root dnf -y install docker-compose-plugin
          elif command -v yum >/dev/null 2>&1; then
            run_as_root yum -y install yum-utils ca-certificates
            command -v curl >/dev/null 2>&1 || run_as_root yum -y install curl
            configure_docker_rpm_repo rhel yum
            run_as_root yum -y install docker-compose-plugin
          else
            fail "dnf or yum is required to install Docker Compose on ${OS_ID}."
          fi
          ;;
        *)
          fail "Unsupported Linux distribution '${OS_ID:-unknown}'. Docker is installed, but Docker Compose cannot be installed automatically."
          ;;
      esac
      ;;
  esac
}

install_docker_for_detected_os() {
  case "$OS_ID" in
    debian)
      install_docker_debian_like debian
      ;;
    ubuntu)
      install_docker_debian_like ubuntu
      ;;
    rhel|rocky|almalinux|ol)
      install_docker_rhel_like rhel
      ;;
    centos)
      install_docker_rhel_like centos
      ;;
    fedora)
      install_docker_rhel_like fedora
      ;;
    alpine)
      install_docker_alpine
      ;;
    *)
      case " $OS_ID_LIKE " in
        *" debian "*)
          install_docker_debian_like debian
          ;;
        *" rhel "*|*" fedora "*)
          install_docker_rhel_like rhel
          ;;
        *)
          fail "Unsupported Linux distribution '${OS_ID:-unknown}'. Supported families: Debian, Ubuntu, RHEL, Rocky, AlmaLinux, Oracle Linux, CentOS Stream, Fedora, Alpine."
          ;;
      esac
      ;;
  esac
}

install_docker_if_needed() {
  if COMPOSE_CMD=$(detect_compose_cmd); then
    log "Docker Compose is already available: ${COMPOSE_CMD}"
    if ensure_docker_running; then
      return
    fi
    fail "Docker is installed but the Docker daemon is not running."
  fi

  load_os_release

  if command -v docker >/dev/null 2>&1; then
    log "Docker is installed but Docker Compose is missing; installing Compose support..."
    install_compose_for_detected_os
  else
    log "Docker is not installed; installing Docker and Docker Compose..."
    install_docker_for_detected_os
  fi

  if ! ensure_docker_running; then
    fail "Docker is installed but the Docker daemon is not running."
  fi

  if COMPOSE_CMD=$(detect_compose_cmd); then
    log "Docker Compose is available: ${COMPOSE_CMD}"
    return
  fi

  fail "Docker Compose is missing, please install docker compose or docker-compose and retry."
}

stop_existing_application() {
  if [ ! -f "${INSTALL_DIR}/docker-compose.yml" ]; then
    return
  fi

  log "Stopping existing application before upgrade backup..."
  (
    cd "$INSTALL_DIR"
    compose_cmd stop >/dev/null 2>&1 || log "Existing application was not running or could not be stopped; continuing."
  )
}

backup_existing_installation() {
  if [ ! -d "$INSTALL_DIR" ]; then
    return
  fi

  BACKUP_ITEMS=""
  for item in .env data logs; do
    if run_as_root test -e "${INSTALL_DIR}/${item}"; then
      BACKUP_ITEMS="${BACKUP_ITEMS} ${item}"
    fi
  done

  if [ -z "$BACKUP_ITEMS" ]; then
    log "Existing installation has no .env, data, or logs to back up."
    return
  fi

  BACKUP_DIR="${INSTALL_DIR}/backups"
  BACKUP_FILE="${BACKUP_DIR}/pre-upgrade-$(date +%Y%m%d-%H%M%S).tar.gz"

  run_as_root mkdir -p "$BACKUP_DIR"
  run_root_sh "cd \"${INSTALL_DIR}\" && tar -czf \"${BACKUP_FILE}\"${BACKUP_ITEMS}"
  log "Preserved current configuration and data backup: ${BACKUP_FILE}"
}

download_and_extract_package() {
  CALLER_DIR=$(pwd -P)
  WORK_DIR=$(mktemp -d "${CALLER_DIR}/.sms-gateway-install.XXXXXX")
  PACKAGE_FILE="${WORK_DIR}/sms-gateway-deploy.tar.gz"
  EXTRACT_DIR="${WORK_DIR}/extract"
  SHA256_FILE="${WORK_DIR}/sms-gateway-deploy.tar.gz.sha256"

  mkdir -p "$EXTRACT_DIR"

  log "Downloading ${APP_NAME} deploy package..."
  curl -fL --retry 5 --retry-delay 2 --retry-all-errors --connect-timeout 20 "$PACKAGE_URL" -o "$PACKAGE_FILE"

  log "Checking downloaded package..."
  if ! tar -tzf "$PACKAGE_FILE" >/dev/null 2>&1; then
    fail "downloaded package is incomplete or corrupted. Delete ${PACKAGE_FILE} and retry."
  fi

  SHA256_URL="${SMS_GATEWAY_SHA256_URL:-${PACKAGE_URL}.sha256}"
  if curl -fL --retry 2 --connect-timeout 10 "$SHA256_URL" -o "$SHA256_FILE" >/dev/null 2>&1; then
    log "Verifying package checksum..."
    EXPECTED_SHA256=$(awk '{ print $1; exit }' "$SHA256_FILE")
    if [ -z "$EXPECTED_SHA256" ]; then
      fail "checksum file is empty: ${SHA256_URL}"
    fi
    (
      cd "$WORK_DIR"
      printf '%s  sms-gateway-deploy.tar.gz\n' "$EXPECTED_SHA256" | sha256sum -c -
    )
  else
    log "Checksum file not found; gzip/tar integrity check passed."
  fi

  REQUIRED_KB=700000
  AVAILABLE_KB=$(df -Pk "$CALLER_DIR" | awk 'NR==2 { print $4 }')
  if [ "${AVAILABLE_KB:-0}" -lt "$REQUIRED_KB" ]; then
    fail "not enough free space in ${CALLER_DIR}. Need at least 700 MB free for download and extraction."
  fi

  tar -xzf "$PACKAGE_FILE" -C "$EXTRACT_DIR"

  if [ ! -x "${EXTRACT_DIR}/sms-gateway/release/install.sh" ]; then
    fail "invalid deploy package: release/install.sh is missing or not executable."
  fi

  INSTALL_PARENT=$(dirname "$INSTALL_DIR")
  run_as_root mkdir -p "$INSTALL_PARENT"

  if [ -d "$INSTALL_DIR" ]; then
    log "Updating existing installation at ${INSTALL_DIR}..."
    stop_existing_application
    backup_existing_installation
    run_as_root cp -a "${EXTRACT_DIR}/sms-gateway/." "$INSTALL_DIR/"
  else
    log "Installing to ${INSTALL_DIR}..."
    if mv "${EXTRACT_DIR}/sms-gateway" "$INSTALL_DIR" 2>/dev/null; then
      :
    else
      run_as_root cp -a "${EXTRACT_DIR}/sms-gateway" "$INSTALL_DIR"
    fi
  fi

  run_as_root chmod +x "${INSTALL_DIR}/release/install.sh"
}

run_application_installer() {
  log "Starting ${APP_NAME}..."
  (
    cd "$INSTALL_DIR"
    run_as_root sh ./release/install.sh
  )
}

read_env_value() {
  key="$1"
  default_value="$2"
  env_file="${INSTALL_DIR}/.env"

  if [ -r "$env_file" ]; then
    value=$(grep -E "^${key}=" "$env_file" | tail -n 1 | cut -d= -f2- || true)
    if [ -n "$value" ]; then
      printf '%s' "$value" | sed "s/^['\"]//;s/['\"]$//"
      return
    fi
  fi

  printf '%s' "$default_value"
}

detect_host_ip() {
  if command -v ip >/dev/null 2>&1; then
    ip -4 route get 1.1.1.1 2>/dev/null | awk '{ for (i=1; i<=NF; i++) if ($i == "src") { print $(i+1); exit } }'
    return
  fi

  if command -v hostname >/dev/null 2>&1; then
    hostname -I 2>/dev/null | awk '{ print $1 }'
    return
  fi
}

print_summary() {
  FRONTEND_PORT=$(read_env_value FRONTEND_PORT 4674)
  ADMIN_USER=$(read_env_value SUPERADMIN_USERNAME admin)
  ADMIN_PASS=$(read_env_value SUPERADMIN_PASSWORD "SMS@openvox")
  HOST_IP=$(detect_host_ip || true)
  HOST_IP="${HOST_IP:-<server-ip>}"

  cat <<EOF

${APP_NAME} deployment completed.

Access URL:
  http://${HOST_IP}:${FRONTEND_PORT}

Default login:
  Username: ${ADMIN_USER}
  Password: ${ADMIN_PASS}

Install directory:
  ${INSTALL_DIR}

Useful commands:
  cd ${INSTALL_DIR}
  ${COMPOSE_CMD:-docker compose} ps
  docker logs -f smsgateway-backend

EOF
}

main() {
  require_package_url
  install_docker_if_needed
  download_and_extract_package
  run_application_installer
  print_summary
}

main "$@"
