#!/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="$(extract_compose_image_ref)"
  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 ""
}

extract_compose_image_ref() {
  awk '$1=="image:"{print $2; exit}' "${COMPOSE_FILE}" | tr -d '"'
}

is_valid_ipv4() {
  local ip="$1"
  local o1
  local o2
  local o3
  local o4

  if [[ ! "${ip}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    return 1
  fi

  IFS='.' read -r o1 o2 o3 o4 <<<"${ip}"
  ((o1 <= 255 && o2 <= 255 && o3 <= 255 && o4 <= 255))
}

cleanup_existing_slave_deployment() {
  local name
  local container_id
  local image_id
  local compose_image_ref

  log "Removing existing intano slave container/image if present"
  for name in intano-slave intanoslave; do
    container_id="$(docker ps -aq --filter "name=^/${name}$" | head -n 1 || true)"
    if [[ -z "${container_id}" ]]; then
      continue
    fi

    image_id="$(docker inspect --format '{{.Image}}' "${container_id}" 2>/dev/null || true)"
    docker rm -f "${container_id}" >/dev/null || true
    log "Removed container: ${name}"

    if [[ -n "${image_id}" ]]; then
      docker rmi -f "${image_id}" >/dev/null || true
      log "Removed container image id: ${image_id}"
    fi
  done

  compose_image_ref="$(extract_compose_image_ref || true)"
  if [[ -z "${compose_image_ref}" ]]; then
    return
  fi

  docker rmi -f "${compose_image_ref}" >/dev/null || true
  log "Removed compose image ref if existed: ${compose_image_ref}"
}

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 is_valid_ipv4 "${host_ip}"; then
      echo "${host_ip}"
      return
    fi
    warn "Invalid IP format, please enter a valid IPv4 address."
  done
}

prompt_bridge_target_ip() {
  local bridge_ip
  while true; do
    read -r -p "Bridge target slave IP: " bridge_ip
    if is_valid_ipv4 "${bridge_ip}"; then
      echo "${bridge_ip}"
      return
    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}"
}

write_bridge_override() {
  local bridge_ip="$1"
  touch "${ENV_FILE}"

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

  log "Updated title slave bridge target override: ${bridge_ip}"
}

clear_bridge_override() {
  touch "${ENV_FILE}"
  if grep -q '^CFG_CORE_TITLE_SLAVE_BRIDGE_TARGET_IP=' "${ENV_FILE}"; then
    sed -i '/^CFG_CORE_TITLE_SLAVE_BRIDGE_TARGET_IP=/d' "${ENV_FILE}"
    log "Cleared title slave bridge target override"
  else
    log "Title slave bridge target override not set, skip clearing"
  fi
}

configure_bridge_override() {
  local answer
  local bridge_ip

  read -r -p "Configure title slave bridge target now? [y/N]: " answer
  case "${answer,,}" in
    y|yes)
      bridge_ip="$(prompt_bridge_target_ip)"
      write_bridge_override "${bridge_ip}"
      ;;
    *)
      clear_bridge_override
      ;;
  esac
}

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}"
  configure_bridge_override

  cleanup_existing_slave_deployment

  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 "$@"
