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

APP_DIR="/opt/intano"
COMPOSE_FILE="${APP_DIR}/docker-compose.slave.yml"
ENV_FILE="${APP_DIR}/docker/intano-slave.env"
DEFAULT_COMPOSE_SOURCE="registry.bocchi.ink/fuyu/intano-compose:latest"
COMPOSE_SOURCE="${INTANO_COMPOSE_SOURCE:-${INTANO_COMPOSE_URL:-${DEFAULT_COMPOSE_SOURCE}}}"
COMPOSE_ARTIFACT_FILE="${INTANO_COMPOSE_ARTIFACT_FILE:-docker-compose.slave.yml}"
COMPOSE_CMD=()
LOGGED_IN_REGISTRIES=()

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

warn() {
  printf '[WARN] %s\n' "$*" >&2
}

die() {
  printf '[ERROR] %s\n' "$*" >&2
  exit 1
}

command_exists() {
  command -v "$1" >/dev/null 2>&1
}

is_http_source() {
  [[ "$1" =~ ^https?:// ]]
}

extract_source_registry_host() {
  local source="$1"

  if is_http_source "${source}"; then
    echo ""
    return
  fi

  echo "${source%%/*}"
}

is_registry_logged_in() {
  local registry_host="$1"
  local item

  for item in "${LOGGED_IN_REGISTRIES[@]:-}"; do
    if [[ "${item}" == "${registry_host}" ]]; then
      return 0
    fi
  done

  return 1
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "Please run this script as root."
  fi
}

check_ubuntu() {
  if [[ ! -f /etc/os-release ]]; then
    die "Cannot find /etc/os-release to detect OS."
  fi

  # shellcheck disable=SC1091
  source /etc/os-release
  if [[ "${ID:-}" != "ubuntu" ]]; then
    die "This script supports Ubuntu only, current: ${ID:-unknown}."
  fi
}

update_system() {
  log "Updating apt indexes and installed packages"
  apt-get update -y
  DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
}

ensure_fetch_tool() {
  if command_exists curl || command_exists wget; then
    return
  fi

  log "curl/wget not found, installing curl"
  apt-get update -y
  DEBIAN_FRONTEND=noninteractive apt-get install -y curl
}

ensure_oras() {
  if command_exists oras; then
    return
  fi

  log "oras not found, installing"
  apt-get update -y
  if DEBIAN_FRONTEND=noninteractive apt-get install -y oras; then
    log "oras installed via apt"
  else
    warn "apt install oras failed, trying official binary"
    ensure_fetch_tool

    local oras_version
    local oras_tag
    local arch
    local download_url
    local archive_path
    oras_tag="${ORAS_VERSION:-v1.2.3}"
    oras_version="${oras_tag#v}"
    arch="$(uname -m)"

    case "${arch}" in
      x86_64) arch="amd64" ;;
      aarch64|arm64) arch="arm64" ;;
      *) die "Unsupported CPU architecture: ${arch}" ;;
    esac

    download_url="https://github.com/oras-project/oras/releases/download/${oras_tag}/oras_${oras_version}_linux_${arch}.tar.gz"
    archive_path="/tmp/oras_${oras_version}_linux_${arch}.tar.gz"

    if command_exists curl; then
      curl -fsSL "${download_url}" -o "${archive_path}"
    else
      wget -qO "${archive_path}" "${download_url}"
    fi

    tar -xzf "${archive_path}" -C /tmp oras
    install -m 0755 /tmp/oras /usr/local/bin/oras
    rm -f /tmp/oras "${archive_path}"
  fi

  if ! command_exists oras; then
    die "oras install failed, please install it manually and retry."
  fi
}

ensure_docker() {
  if ! command_exists docker; then
    log "Docker not found, installing docker.io"
    apt-get update -y
    DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose-plugin || \
      DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose
  fi

  if command_exists systemctl; then
    log "Enabling and starting Docker service"
    systemctl enable --now docker
  fi

  if ! docker info >/dev/null 2>&1; then
    die "Docker daemon is not ready, please check service status."
  fi
}

resolve_compose_cmd() {
  if docker compose version >/dev/null 2>&1; then
    COMPOSE_CMD=(docker compose)
    return
  fi

  if command_exists docker-compose; then
    COMPOSE_CMD=(docker-compose)
    return
  fi

  log "Docker Compose not found, installing"
  apt-get update -y
  DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin || \
    DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose

  if docker compose version >/dev/null 2>&1; then
    COMPOSE_CMD=(docker compose)
    return
  fi

  if command_exists docker-compose; then
    COMPOSE_CMD=(docker-compose)
    return
  fi

  die "Docker Compose install failed."
}

registry_login() {
  local registry_host="$1"

  if [[ -z "${registry_host}" ]]; then
    warn "No private registry host detected, skip login."
    return
  fi

  if is_registry_logged_in "${registry_host}"; then
    log "Registry already logged in: ${registry_host}"
    return
  fi

  local registry_user
  local registry_pass

  read -r -p "Registry username (${registry_host}): " registry_user
  if [[ -z "${registry_user}" ]]; then
    die "Registry username cannot be empty."
  fi

  read -r -s -p "Registry password (input hidden): " registry_pass
  echo ""
  if [[ -z "${registry_pass}" ]]; then
    die "Registry password cannot be empty."
  fi

  log "Logging in to registry: ${registry_host}"
  printf '%s' "${registry_pass}" | docker login "${registry_host}" -u "${registry_user}" --password-stdin
  if command_exists oras; then
    printf '%s' "${registry_pass}" | oras login "${registry_host}" -u "${registry_user}" --password-stdin
  fi

  LOGGED_IN_REGISTRIES+=("${registry_host}")
  unset registry_pass
}

pull_compose_from_http() {
  if command_exists curl; then
    curl -fsSL "${COMPOSE_SOURCE}" -o "${COMPOSE_FILE}"
    return
  fi

  if command_exists wget; then
    wget -qO "${COMPOSE_FILE}" "${COMPOSE_SOURCE}"
    return
  fi

  die "Neither curl nor wget is available for HTTP compose source."
}

pull_compose_from_oci() {
  local source_registry_host
  local tmp_dir
  local artifact_path

  ensure_oras
  source_registry_host="$(extract_source_registry_host "${COMPOSE_SOURCE}")"
  tmp_dir="$(mktemp -d)"

  if ! oras pull "${COMPOSE_SOURCE}" -o "${tmp_dir}" >/dev/null 2>&1; then
    warn "Anonymous pull failed for ${COMPOSE_SOURCE}, trying registry login and retry"
    registry_login "${source_registry_host}"
    oras pull "${COMPOSE_SOURCE}" -o "${tmp_dir}" >/dev/null || \
      die "Failed to pull compose artifact: ${COMPOSE_SOURCE}"
  fi

  artifact_path="${tmp_dir}/${COMPOSE_ARTIFACT_FILE}"
  if [[ ! -f "${artifact_path}" ]]; then
    artifact_path="$(find "${tmp_dir}" -type f -name "${COMPOSE_ARTIFACT_FILE}" | head -n 1 || true)"
  fi

  if [[ -z "${artifact_path}" || ! -f "${artifact_path}" ]]; then
    rm -rf "${tmp_dir}"
    die "Compose file '${COMPOSE_ARTIFACT_FILE}' not found in artifact ${COMPOSE_SOURCE}."
  fi

  cp "${artifact_path}" "${COMPOSE_FILE}"
  rm -rf "${tmp_dir}"
}

pull_compose_file() {
  log "Preparing app directory: ${APP_DIR}"
  mkdir -p "${APP_DIR}" "${APP_DIR}/docker" "${APP_DIR}/logs"

  log "Fetching compose source: ${COMPOSE_SOURCE}"
  if is_http_source "${COMPOSE_SOURCE}"; then
    pull_compose_from_http
  else
    pull_compose_from_oci
  fi

  if [[ ! -s "${COMPOSE_FILE}" ]]; then
    die "Compose file is empty or missing: ${COMPOSE_FILE}"
  fi
}

extract_registry_host() {
  local image_ref
  image_ref="$(awk '$1=="image:"{print $2; exit}' "${COMPOSE_FILE}" | tr -d '"')"
  if [[ -z "${image_ref}" ]]; then
    die "Cannot parse image reference from compose file."
  fi

  local registry_host
  registry_host="${image_ref%%/*}"

  if [[ "${registry_host}" == "${image_ref}" ]]; then
    echo ""
    return
  fi

  if [[ "${registry_host}" == *.* || "${registry_host}" == *:* || "${registry_host}" == "localhost" ]]; then
    echo "${registry_host}"
    return
  fi

  echo ""
}

prompt_host_ip() {
  local detected_ip
  detected_ip="$(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}}')"
  if [[ -z "${detected_ip}" ]]; then
    detected_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  fi
  if [[ -z "${detected_ip}" ]]; then
    detected_ip="127.0.0.1"
  fi

  local host_ip
  while true; do
    read -r -p "Server hostname IP [${detected_ip}]: " host_ip
    host_ip="${host_ip:-${detected_ip}}"

    if [[ "${host_ip}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
      IFS='.' read -r o1 o2 o3 o4 <<<"${host_ip}"
      if ((o1 <= 255 && o2 <= 255 && o3 <= 255 && o4 <= 255)); then
        echo "${host_ip}"
        return
      fi
    fi

    warn "Invalid IP format, please enter a valid IPv4 address."
  done
}

write_env_override() {
  local host_ip="$1"
  touch "${ENV_FILE}"

  if grep -q '^CFG_CORE_SERVER_HOSTNAME=' "${ENV_FILE}"; then
    sed -i "s|^CFG_CORE_SERVER_HOSTNAME=.*|CFG_CORE_SERVER_HOSTNAME=${host_ip}|" "${ENV_FILE}"
  else
    printf '\nCFG_CORE_SERVER_HOSTNAME=%s\n' "${host_ip}" >> "${ENV_FILE}"
  fi

  log "Updated server hostname override: ${host_ip}"
}

pull_and_start() {
  log "Pulling images"
  "${COMPOSE_CMD[@]}" -f "${COMPOSE_FILE}" pull

  log "Starting containers"
  "${COMPOSE_CMD[@]}" -f "${COMPOSE_FILE}" up -d
}

configure_ufw() {
  if ! command_exists ufw; then
    warn "ufw not found, skip firewall configuration."
    return
  fi

  log "Allowing UFW port 8080/tcp"
  ufw allow 8080/tcp >/dev/null

  local status_line
  status_line="$(ufw status | head -n 1 || true)"
  if [[ "${status_line}" == "Status: inactive" ]]; then
    log "UFW is inactive, enabling now"
    ufw allow OpenSSH >/dev/null || true
    ufw --force enable >/dev/null
  fi
}

main() {
  require_root
  check_ubuntu
  update_system
  ensure_fetch_tool
  ensure_docker
  resolve_compose_cmd
  pull_compose_file

  local registry_host
  registry_host="$(extract_registry_host)"
  registry_login "${registry_host}"

  local host_ip
  host_ip="$(prompt_host_ip)"
  write_env_override "${host_ip}"

  pull_and_start
  configure_ufw

  log "Deployment completed"
  log "Compose file: ${COMPOSE_FILE}"
  log "Environment override: ${ENV_FILE}"
  log "Current server.hostname: ${host_ip}"
}

main "$@"
