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

readonly BLIP_DEFAULT_REPOSITORY="https://gitlab.com/almateraincubator/utilities/blip.git"
readonly BLIP_DEFAULT_BRANCH="dev"
readonly BLIP_BINARY_PATH="/usr/local/bin/blip"
readonly BLIP_LEGACY_CONFIG_PATH="/etc/blip/blip.toml"

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

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

[[ "$(id -u)" -eq 0 ]] || fail "run this installer with sudo"

build_user="${BLIP_USER:-${SUDO_USER:-}}"
[[ -n "$build_user" && "$build_user" != "root" ]] || \
  fail "set BLIP_USER to the non-root account used to build Blip"
id "$build_user" >/dev/null 2>&1 || fail "build user does not exist: $build_user"

build_home="$(getent passwd "$build_user" | cut -d: -f6)"
build_group="$(id -gn "$build_user")"
[[ -n "$build_home" && -d "$build_home" ]] || fail "cannot determine home directory for $build_user"

repo_url="${BLIP_REPO_URL:-$BLIP_DEFAULT_REPOSITORY}"
repo_branch="${BLIP_REPO_BRANCH:-$BLIP_DEFAULT_BRANCH}"
source_dir="${BLIP_SOURCE_DIR:-/usr/local/src/blip}"
service_user="${BLIP_SERVICE_USER:-$build_user}"
service_group=""
installed_service_user=""
upgrade_only="${BLIP_UPGRADE_ONLY:-0}"
build_path="$build_home/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

[[ "$source_dir" = /* ]] || fail "BLIP_SOURCE_DIR must be an absolute path"
case "$source_dir" in
  /|/etc|/home|/opt|/tmp|/usr|/usr/local|/var|/var/lib)
    fail "BLIP_SOURCE_DIR is too broad: $source_dir"
    ;;
esac
[[ "$service_user" != "root" ]] || fail "BLIP_SERVICE_USER must not be root"
[[ "$upgrade_only" == "0" || "$upgrade_only" == "1" ]] || \
  fail "BLIP_UPGRADE_ONLY must be 0 or 1"

if [[ "$upgrade_only" == "1" ]]; then
  installed_service_user="$(systemctl show blip.service --property=User --value)"
  [[ -n "$installed_service_user" && "$installed_service_user" != "root" ]] || \
    fail "cannot determine the installed non-root service user"
  service_user="$installed_service_user"
fi

if ! id "$service_user" >/dev/null 2>&1; then
  command -v useradd >/dev/null 2>&1 || fail "cannot create service user because useradd is unavailable"
  log "creating service account: $service_user"
  useradd --system --create-home --shell /usr/sbin/nologin "$service_user"
fi
service_group="$(id -gn "$service_user")"
service_home="$(getent passwd "$service_user" | cut -d: -f6)"
[[ -n "$service_home" && -d "$service_home" ]] || fail "cannot determine home directory for $service_user"
data_dir="$service_home/.local/share/blip"
config_path="${BLIP_CONFIG_PATH:-$data_dir/blip.toml}"
[[ "$config_path" = /* ]] || fail "BLIP_CONFIG_PATH must be an absolute path"
install -d -m 0750 -o "$service_user" -g "$service_group" "$data_dir"

run_as_builder() {
  runuser -u "$build_user" -- env \
    HOME="$build_home" \
    USER="$build_user" \
    LOGNAME="$build_user" \
    PATH="$build_path" \
    "$@"
}

install_build_dependencies() {
  if [[ "${BLIP_INSTALL_DEPENDENCIES:-1}" != "1" ]]; then
    fail "Git, curl, and a C compiler are required; install them or set BLIP_INSTALL_DEPENDENCIES=1"
  fi

  if command -v apt-get >/dev/null 2>&1; then
    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential ca-certificates curl git
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y ca-certificates curl gcc git
  elif command -v zypper >/dev/null 2>&1; then
    zypper --non-interactive install -y ca-certificates curl gcc git
  elif command -v pacman >/dev/null 2>&1; then
    pacman --sync --refresh --needed --noconfirm base-devel ca-certificates curl git
  else
    fail "unsupported package manager; install Git, curl, and a C compiler manually"
  fi
}

readonly BLIP_GITHUB_REPO="zamkara/blip"
install_method="${BLIP_INSTALL_METHOD:-source}"
[[ "$install_method" == "source" || "$install_method" == "binary" || "$install_method" == "crates" ]] || \
  fail "BLIP_INSTALL_METHOD must be 'source', 'crates', or 'binary'"

detect_target() {
  local os arch
  os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  arch="$(uname -m)"

  case "$os" in
    linux)
      case "$arch" in
        x86_64) echo "x86_64-unknown-linux-musl" ;;
        aarch64|arm64) echo "aarch64-unknown-linux-musl" ;;
        armv7l|armhf) echo "armv7-unknown-linux-musleabihf" ;;
        riscv64) echo "riscv64gc-unknown-linux-gnu" ;;
        *) fail "unsupported Linux architecture: $arch" ;;
      esac
      ;;
    darwin)
      case "$arch" in
        x86_64) echo "x86_64-apple-darwin" ;;
        arm64) echo "aarch64-apple-darwin" ;;
        *) fail "unsupported macOS architecture: $arch" ;;
      esac
      ;;
    *)
      fail "unsupported operating system: $os"
      ;;
  esac
}

install_prebuilt_binary() {
  local target latest_tag download_url temp_dir checksum_file actual_sha expected_sha
  target="$(detect_target)"
  log "detecting pre-built binary for target: $target"

  command -v curl >/dev/null 2>&1 || fail "curl is required to download the pre-built binary"
  command -v tar >/dev/null 2>&1 || fail "tar is required to unpack the pre-built binary"

  latest_tag="$(curl -sL "https://api.github.com/repos/${BLIP_GITHUB_REPO}/releases/latest" | grep -m1 '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' || true)"
  [[ -n "$latest_tag" ]] || fail "could not retrieve latest release tag from ${BLIP_GITHUB_REPO}"

  log "downloading Blip pre-built release: $latest_tag"
  download_url="https://github.com/${BLIP_GITHUB_REPO}/releases/download/${latest_tag}/blip-${target}.tar.gz"
  temp_dir="$(mktemp -d)"

  curl -fsSL "$download_url" -o "$temp_dir/blip-${target}.tar.gz"
  curl -fsSL "https://github.com/${BLIP_GITHUB_REPO}/releases/download/${latest_tag}/SHA256SUMS.txt" -o "$temp_dir/SHA256SUMS.txt" || true

  if [[ -f "$temp_dir/SHA256SUMS.txt" ]] && command -v sha256sum >/dev/null 2>&1; then
    expected_sha="$(grep "blip-${target}.tar.gz" "$temp_dir/SHA256SUMS.txt" | awk '{print $1}' || true)"
    if [[ -n "$expected_sha" ]]; then
      actual_sha="$(sha256sum "$temp_dir/blip-${target}.tar.gz" | awk '{print $1}')"
      [[ "$actual_sha" == "$expected_sha" ]] || fail "checksum verification failed for blip-${target}.tar.gz"
      log "checksum verified ($actual_sha)"
    fi
  fi

  tar -xzf "$temp_dir/blip-${target}.tar.gz" -C "$temp_dir"
  install -m 0755 "$temp_dir/blip-${target}/blip" "$BLIP_BINARY_PATH"
  rm -rf "$temp_dir"
}

install_crates_binary() {
  local temp_dir
  run_as_builder bash -c 'command -v cargo >/dev/null 2>&1' || \
    fail "Cargo is required for $build_user to install Blip from crates.io"
  temp_dir="$(mktemp -d /tmp/blip-crates.XXXXXX)"
  chown "$build_user:$build_group" "$temp_dir"
  run_as_builder env PATH="$temp_dir/bin:$build_path" \
    cargo install bliper --locked --root "$temp_dir"
  install -m 0755 "$temp_dir/bin/blip" "$BLIP_BINARY_PATH"
  rm -rf -- "$temp_dir"
}

if [[ "$upgrade_only" == "1" && ! -f "$config_path" && ! -f "$BLIP_LEGACY_CONFIG_PATH" ]]; then
  fail "upgrade requires an existing configuration: $config_path"
fi

if [[ "$install_method" == "binary" ]]; then
  install_prebuilt_binary
elif [[ "$install_method" == "crates" ]]; then
  install_crates_binary
else
  missing_build_tool=0
  for command_name in git curl cc; do
    command -v "$command_name" >/dev/null 2>&1 || missing_build_tool=1
  done
  if [[ "$missing_build_tool" -eq 1 ]]; then
    log "installing native build dependencies"
    install_build_dependencies
  fi

  for command_name in getent grep install runuser systemctl; do
    command -v "$command_name" >/dev/null 2>&1 || fail "required command is missing: $command_name"
  done

  rustup_installer=""
  cleanup() {
    [[ -z "$rustup_installer" || ! -f "$rustup_installer" ]] || rm -f -- "$rustup_installer"
  }
  trap cleanup EXIT

  if ! run_as_builder bash -c 'command -v cargo >/dev/null 2>&1'; then
    log "installing the stable Rust toolchain for $build_user"
    rustup_installer="$(mktemp)"
    curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o "$rustup_installer"
    chown "$build_user:$build_group" "$rustup_installer"
    chmod 0700 "$rustup_installer"
    run_as_builder "$rustup_installer" -y --profile minimal --default-toolchain stable
  fi

  if run_as_builder bash -c 'command -v rustup >/dev/null 2>&1'; then
    log "selecting the stable Rust toolchain for $build_user"
    run_as_builder rustup toolchain install stable --profile minimal
    run_as_builder rustup default stable
    cargo_command=(cargo +stable)
  else
    cargo_command=(cargo)
  fi

  source_parent="$(dirname "$source_dir")"
  [[ -d "$source_parent" ]] || install -d -m 0755 "$source_parent"
  [[ ! -L "$source_dir" ]] || fail "source path must not be a symbolic link: $source_dir"

  if [[ -e "$source_dir" && ! -d "$source_dir/.git" ]]; then
    if [[ ! -d "$source_dir" || -n "$(find "$source_dir" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
      fail "source path exists but is not an empty directory or Git checkout: $source_dir"
    fi
  fi

  if [[ ! -d "$source_dir/.git" ]]; then
    log "cloning Blip into $source_dir"
    install -d -m 0755 -o "$build_user" -g "$build_group" "$source_dir"
    run_as_builder git clone --branch "$repo_branch" --single-branch "$repo_url" "$source_dir"
  else
    current_origin="$(run_as_builder git -C "$source_dir" remote get-url origin)"
    [[ "$current_origin" == "$repo_url" ]] || \
      fail "source checkout origin is $current_origin, expected $repo_url"
    [[ -z "$(run_as_builder git -C "$source_dir" status --porcelain)" ]] || \
      fail "source checkout contains local changes: $source_dir"
    log "updating the existing source checkout"
    run_as_builder git -C "$source_dir" fetch origin "$repo_branch"
    if run_as_builder git -C "$source_dir" show-ref --verify --quiet "refs/heads/$repo_branch"; then
      run_as_builder git -C "$source_dir" switch "$repo_branch"
    else
      run_as_builder git -C "$source_dir" switch --create "$repo_branch" --track "origin/$repo_branch"
    fi
    run_as_builder git -C "$source_dir" merge --ff-only "origin/$repo_branch"
  fi

  log "building the release binary"
  run_as_builder "${cargo_command[@]}" build --locked --release --manifest-path "$source_dir/Cargo.toml"
  install -m 0755 "$source_dir/target/release/blip" "$BLIP_BINARY_PATH"
fi

if [[ ! -f "$config_path" && -f "$BLIP_LEGACY_CONFIG_PATH" ]]; then
  log "migrating legacy runtime files into $data_dir"
  if systemctl cat blip.service >/dev/null 2>&1; then
    systemctl stop blip.service
  fi
  migration_backup="$BLIP_LEGACY_CONFIG_PATH.$(date -u +%Y%m%dT%H%M%SZ).migrated.bak"
  mv "$BLIP_LEGACY_CONFIG_PATH" "$migration_backup"
  cp --preserve=mode,ownership "$migration_backup" "$config_path"
  chown root:"$service_group" "$config_path"
  chmod 0640 "$config_path"
  for runtime_file in blip-history.jsonl blip-deliveries.jsonl blip.queue.lock; do
    if [[ -e "/var/lib/blip/$runtime_file" && ! -e "$data_dir/$runtime_file" ]]; then
      mv "/var/lib/blip/$runtime_file" "$data_dir/$runtime_file"
      chown "$service_user:$service_group" "$data_dir/$runtime_file"
    fi
  done
  if grep -Fq 'history_file = "/var/lib/blip/blip-history.jsonl"' "$config_path"; then
    "$BLIP_BINARY_PATH" --config "$config_path" config set --history-file blip-history.jsonl
  fi
  log "legacy configuration backup: $migration_backup"
fi

if [[ "$upgrade_only" == "1" ]]; then
  log "validating existing configuration"
  "$BLIP_BINARY_PATH" --config "$config_path" config validate
  "$BLIP_BINARY_PATH" --config "$config_path" service install --user "$installed_service_user"
  log "upgrade complete: $("$BLIP_BINARY_PATH" --version)"
  exit 0
fi

prompt_value() {
  local variable_name="$1"
  local label="$2"
  local default_value="${3:-}"
  local current_value="${!variable_name:-}"

  [[ -n "$current_value" ]] && return
  if [[ ! -r /dev/tty ]]; then
    [[ -n "$default_value" ]] || fail "$variable_name is required in non-interactive mode"
    printf -v "$variable_name" '%s' "$default_value"
    return
  fi
  if [[ -n "$default_value" ]]; then
    read -r -p "$label [$default_value]: " current_value </dev/tty
    current_value="${current_value:-$default_value}"
  else
    read -r -p "$label: " current_value </dev/tty
  fi
  printf -v "$variable_name" '%s' "$current_value"
}

configure_required=0
if [[ ! -f "$config_path" ]]; then
  configure_required=1
elif [[ "${BLIP_RECONFIGURE:-0}" == "1" ]]; then
  log "configuration replacement requested"
  configure_required=1
elif grep -Eq '^[[:space:]]*\[\[projects\]\][[:space:]]*(#.*)?$' "$config_path"; then
  log "legacy project-array configuration detected; it will be backed up and replaced"
  configure_required=1
else
  log "preserving existing configuration: $config_path"
fi

if [[ "$configure_required" -eq 1 ]]; then
  project_key="${BLIP_PROJECT_KEY:-}"
  deploy_script="${BLIP_SCRIPT:-}"
  provider="${BLIP_PROVIDER:-}"
  signing_token="${BLIP_SIGNING_TOKEN:-}"
  secret_token="${BLIP_SECRET_TOKEN:-${BLIP_SECRET:-}}"

  prompt_value project_key "Project key used in the webhook URL"
  prompt_value deploy_script "Absolute path to the deployment executable"
  prompt_value provider "Webhook provider (gitlab, github, gitea, codeberg)" "gitlab"

  [[ "$project_key" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]] || \
    fail "project key may contain only letters, digits, dots, underscores, and hyphens"
  [[ "$deploy_script" = /* ]] || fail "deployment executable must use an absolute path"
  [[ -x "$deploy_script" ]] || fail "deployment executable is missing or not executable: $deploy_script"
  [[ -f "$deploy_script" ]] || fail "deployment executable must be a file: $deploy_script"
  runuser -u "$service_user" -- test -x "$deploy_script" || \
    fail "$service_user cannot execute $deploy_script"
  case "$provider" in
    gitlab|github|gitea|codeberg) ;;
    *) fail "unsupported webhook provider: $provider" ;;
  esac
  if [[ "$provider" != "gitlab" && -n "$signing_token" ]]; then
    fail "BLIP_SIGNING_TOKEN is available only for GitLab"
  fi

  if [[ -f "$config_path" ]]; then
    backup_path="$config_path.$(date -u +%Y%m%dT%H%M%SZ).bak"
    cp --preserve=mode,ownership "$config_path" "$backup_path"
    log "saved the previous configuration to $backup_path"
    rm -f -- "$config_path"
  fi

  project_command=(
    "$BLIP_BINARY_PATH"
    --config "$config_path"
    project add
    --key "$project_key"
    --script "$deploy_script"
  )
  if [[ "$provider" != "gitlab" ]]; then
    project_command+=(--provider "$provider")
  fi
  if [[ -n "$signing_token" ]]; then
    project_command+=(--signing-token "$signing_token")
  elif [[ -n "$secret_token" ]]; then
    project_command+=(--secret-token "$secret_token")
  fi
  "${project_command[@]}"
fi

log "validating configuration"
if ! "$BLIP_BINARY_PATH" --config "$config_path" config validate; then
  fail "configuration is invalid; fix it or rerun with BLIP_RECONFIGURE=1 to replace it"
fi

"$BLIP_BINARY_PATH" --config "$config_path" service install --user "$service_user"

log "installation complete"
log "configuration: $config_path"
log "service status: blip service status"
log "webhook path: /webhook/<project-key>"
