#!/bin/sh

set -eu
LC_ALL=C
export LC_ALL

user_path=${PATH:-}
PATH="$HOME/.local/bin:$user_path:/opt/homebrew/bin:/usr/local/bin"
export PATH

download_base_url=${OBLIVE_DOWNLOAD_BASE_URL:-https://downloads.oblive.dev}
download_base_url=${download_base_url%/}
asset_name=oblive-deployment.tar.gz
channel=latest
requested_version=""
install_dir=${OBLIVE_HOME:-}
instance=default
port_base=""
auth_mode=local
start_after_install=true
path_choice=ask
install_prerequisites=false
assume_yes=false

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

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

case "$download_base_url" in https://*) ;; *) fail "the download base URL must use HTTPS" ;; esac

usage() {
  say "Usage: install.sh [--channel latest|--version vX.Y.Z] [--install-dir path]"
  say "                  [--instance name --port-base number]"
  say "                  [--auth local|api-key] [--no-start]"
  say "                  [--add-to-path|--no-add-to-path]"
  say "                  [--install-prerequisites] [--yes]"
}

normalize_version() {
  normalized=$1
  case "$normalized" in v*) ;; *) normalized="v$normalized" ;; esac
  printf '%s\n' "$normalized" | awk '
    !/^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/ { exit 1 }
  ' || fail "expected a stable semantic version such as v1.2.3"
  printf '%s\n' "$normalized"
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --channel)
      [ "${2:-}" = latest ] || fail "--channel currently supports only latest"
      channel=latest
      requested_version=""
      shift 2
      ;;
    --version)
      [ -n "${2:-}" ] || fail "--version requires a value"
      requested_version=$(normalize_version "$2")
      channel=pinned
      shift 2
      ;;
    --install-dir)
      [ -n "${2:-}" ] || fail "--install-dir requires a path"
      install_dir=$2
      shift 2
      ;;
    --instance)
      [ -n "${2:-}" ] || fail "--instance requires a name"
      instance=$2
      shift 2
      ;;
    --port-base)
      [ -n "${2:-}" ] || fail "--port-base requires a number"
      port_base=$2
      shift 2
      ;;
    --auth)
      case "${2:-}" in local | api-key) auth_mode=$2 ;; *) fail "--auth must be local or api-key" ;; esac
      shift 2
      ;;
    --no-start) start_after_install=false; shift ;;
    --add-to-path) path_choice=yes; shift ;;
    --no-add-to-path) path_choice=no; shift ;;
    --install-prerequisites) install_prerequisites=true; shift ;;
    --yes) assume_yes=true; shift ;;
    -h | --help) usage; exit 0 ;;
    *) fail "unknown option: $1" ;;
  esac
done

if [ "${#instance}" -gt 48 ] || ! printf '%s\n' "$instance" | awk '
  !/^[a-z0-9][a-z0-9_-]*$/ { exit 1 }
  END { if (NR != 1) exit 1 }
'; then
  fail "instance names must contain up to 48 lowercase letters, digits, dashes, or underscores"
fi
if [ -n "$port_base" ]; then
  printf '%s\n' "$port_base" | awk '
    !/^[1-9][0-9]{3,4}$/ || $0 < 1024 || $0 > 65530 { exit 1 }
  ' || fail "--port-base must be an integer from 1024 to 65530"
fi

config_home=${XDG_CONFIG_HOME:-"$HOME/.config"}/oblive
install_pointer="$config_home/install-root"
default_install_dir="$HOME/.oblive"
if [ "$instance" != default ]; then
  install_pointer="$config_home/instances/$instance"
  default_install_dir="$HOME/.oblive-instances/$instance"
fi
if [ -z "$install_dir" ]; then
  if [ -f "$install_pointer" ]; then install_dir=$(sed -n '1p' "$install_pointer")
  else install_dir=$default_install_dir; fi
fi

case "$install_dir" in
  "~") install_dir=$HOME ;;
  \~/*) install_dir="$HOME/${install_dir#\~/}" ;;
esac
case "$install_dir" in
  "" | / | "$HOME" | *:*) fail "the installation directory is unsafe: $install_dir" ;;
esac
case "$install_dir" in /*) ;; *) install_dir="$(pwd)/$install_dir" ;; esac
printf '%s\n' "$install_dir" | awk 'END { if (NR != 1) exit 1 }' ||
  fail "the installation directory must not contain a newline"

mkdir -p "$(dirname "$install_dir")"
if [ -d "$install_dir" ]; then install_dir=$(cd "$install_dir" && pwd -P)
else install_dir="$(cd "$(dirname "$install_dir")" && pwd -P)/$(basename "$install_dir")"; fi
case "$install_dir" in
  / | "$HOME" | *:*) fail "the installation directory is unsafe: $install_dir" ;;
esac

if [ -f "$install_pointer" ]; then
  registered_root=$(sed -n '1p' "$install_pointer")
  [ "$registered_root" = "$install_dir" ] || [ "$registered_root" -ef "$install_dir" ] ||
    fail "instance '$instance' is already registered at another directory"
fi
for other_pointer in "$config_home/install-root" "$config_home"/instances/*; do
  [ -f "$other_pointer" ] || continue
  [ "$other_pointer" != "$install_pointer" ] || continue
  other_root=$(sed -n '1p' "$other_pointer")
  if [ -d "$other_root" ]; then other_root=$(cd "$other_root" && pwd -P); fi
  case "$install_dir/" in "$other_root/"*) fail "instance directories must not overlap" ;; esac
  case "$other_root/" in "$install_dir/"*) fail "instance directories must not overlap" ;; esac
done
if [ -f "$install_dir/VERSION" ]; then
  installed_instance=default
  if [ -f "$install_dir/.instance" ]; then installed_instance=$(sed -n '1p' "$install_dir/.instance"); fi
  [ "$instance" = "$installed_instance" ] || fail "this directory belongs to instance '$installed_instance'"
fi
if [ "$instance" != default ] && [ ! -f "$install_dir/local-stack.json" ] && [ -z "$port_base" ]; then
  fail "a new named instance requires --port-base (for example 10000, reserving ports 10000-10005)"
fi

confirm() {
  prompt=$1
  default_answer=$2
  if [ "$assume_yes" = true ]; then
    return 0
  fi
  [ -r /dev/tty ] || return 1
  if [ "$default_answer" = yes ]; then
    printf '%s [Y/n] ' "$prompt" >/dev/tty
  else
    printf '%s [y/N] ' "$prompt" >/dev/tty
  fi
  IFS= read -r answer </dev/tty || return 1
  case "$answer" in
    y | Y | yes | YES | Yes) return 0 ;;
    n | N | no | NO | No) return 1 ;;
    "") [ "$default_answer" = yes ] ;;
    *) return 1 ;;
  esac
}

ask() {
  confirm "$1" no
}

os=$(uname -s)
architecture=$(uname -m)
case "$os" in Darwin | Linux) ;; *) fail "supported systems are macOS and Linux" ;; esac
case "$architecture" in x86_64 | amd64 | arm64 | aarch64) ;; *) fail "unsupported architecture: $architecture" ;; esac

for required_command in curl tar awk sed mktemp; do
  command -v "$required_command" >/dev/null 2>&1 || fail "$required_command is required"
done

temporary_root=$(mktemp -d)
trap 'rm -rf "$temporary_root"' EXIT HUP INT TERM

install_homebrew() {
  say "Homebrew is not installed. Its official installer will explain every system change."
  if [ "$install_prerequisites" = true ] || ask "Install Homebrew?"; then
    curl --fail --location --silent --show-error \
      https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh \
      --output "$temporary_root/install-homebrew.sh"
    if [ "$assume_yes" = true ]; then
      NONINTERACTIVE=1 /bin/bash "$temporary_root/install-homebrew.sh"
    else
      /bin/bash "$temporary_root/install-homebrew.sh"
    fi
    if [ -x /opt/homebrew/bin/brew ]; then eval_brew=/opt/homebrew/bin/brew
    elif [ -x /usr/local/bin/brew ]; then eval_brew=/usr/local/bin/brew
    else fail "Homebrew installation finished, but brew was not found"; fi
  else
    fail "Docker installation on macOS requires Homebrew or a manual Docker installation"
  fi
}

install_docker() {
  if [ "$os" = Darwin ]; then
    if command -v brew >/dev/null 2>&1; then eval_brew=$(command -v brew); else install_homebrew; fi
    say "Docker Desktop requires accepting Docker's terms and starting its application."
    if [ "$install_prerequisites" = true ] || ask "Install Docker Desktop with Homebrew?"; then
      "$eval_brew" install --cask docker-desktop
      open -a Docker || true
    else
      fail "Docker is required"
    fi
  else
    say "Docker's official convenience script may install packages and use elevated privileges."
    if [ "$install_prerequisites" = true ] || ask "Inspect Docker's official installation script?"; then
      curl --fail --location --silent --show-error https://get.docker.com \
        --output "$temporary_root/install-docker.sh"
      sh "$temporary_root/install-docker.sh" --dry-run
      if [ "$assume_yes" = true ] || ask "Run the Docker installation shown above?"; then
        sh "$temporary_root/install-docker.sh"
      else
        fail "Docker installation was cancelled"
      fi
    else
      fail "Docker is required"
    fi
  fi
}

if ! command -v docker >/dev/null 2>&1; then
  if [ "$install_prerequisites" = true ] || ask "Docker was not found. Install it now?"; then
    install_docker
  else
    fail "Docker is required; install it and rerun this command"
  fi
fi

if ! docker info >/dev/null 2>&1; then
  if [ "$os" = Darwin ] && command -v open >/dev/null 2>&1; then
    say "Starting Docker Desktop…"
    open -a Docker || true
    attempts=0
    while ! docker info >/dev/null 2>&1 && [ "$attempts" -lt 30 ]; do
      sleep 2
      attempts=$((attempts + 1))
    done
  fi
fi
docker info >/dev/null 2>&1 || fail "Docker is installed but its daemon is not running"
docker compose version >/dev/null 2>&1 || fail "the Docker Compose plugin is required"

if [ "$auth_mode" = local ]; then
  if ! command -v codex >/dev/null 2>&1; then
    say "Oblive local authentication uses the official Codex CLI."
    if [ "$install_prerequisites" = true ] || ask "Install Codex?"; then
      curl --fail --location --silent --show-error https://chatgpt.com/codex/install.sh \
        --output "$temporary_root/install-codex.sh"
      CODEX_NON_INTERACTIVE=1 sh "$temporary_root/install-codex.sh"
      command -v codex >/dev/null 2>&1 || fail "Codex installed, but it is not available on PATH"
    else
      fail "Codex is required for local authentication; use --auth api-key for advanced setup"
    fi
  fi
  if ! codex login status >/dev/null 2>&1; then
    [ -r /dev/tty ] || fail "Codex is not logged in; run 'codex login' and retry"
    say "Opening Codex login…"
    codex login </dev/tty >/dev/tty
    codex login status >/dev/null 2>&1 || fail "Codex login did not complete"
  fi
else
  [ -n "${OPENAI_API_KEY:-}" ] || fail "OPENAI_API_KEY is required with --auth api-key"
fi

bin_dir=${OBLIVE_BIN_DIR:-"$HOME/.local/bin"}
cli_link="$bin_dir/oblive"

path_contains() {
  case ":$1:" in *":$2:"*) return 0 ;; *) return 1 ;; esac
}

resolve_shell_profile() {
  escaped_bin_dir=$(printf '%s\n' "$bin_dir" | sed 's/[\$`"\\]/\\&/g')
  case "${SHELL:-}" in
    */fish)
      shell_profile="$HOME/.config/fish/config.fish"
      path_line="fish_add_path \"$escaped_bin_dir\""
      ;;
    */zsh)
      shell_profile="$HOME/.zshrc"
      path_line="export PATH=\"$escaped_bin_dir:\$PATH\""
      ;;
    */bash)
      if [ "$os" = Darwin ]; then shell_profile="$HOME/.bash_profile"
      else shell_profile="$HOME/.bashrc"; fi
      path_line="export PATH=\"$escaped_bin_dir:\$PATH\""
      ;;
    *)
      shell_profile="$HOME/.profile"
      path_line="export PATH=\"$escaped_bin_dir:\$PATH\""
      ;;
  esac
}

configure_path() {
  path_status=active
  path_contains "$user_path" "$bin_dir" && return

  resolve_shell_profile
  if [ -f "$shell_profile" ] && grep -Fqx "$path_line" "$shell_profile"; then
    path_status=configured
    return
  fi

  if [ "$path_choice" = ask ]; then
    if confirm "Add Oblive to your PATH?" yes; then path_choice=yes
    else path_choice=no; fi
  fi
  if [ "$path_choice" = no ]; then
    path_status=skipped
    return
  fi

  mkdir -p "$(dirname "$shell_profile")"
  printf '\n# Oblive CLI\n%s\n' "$path_line" >> "$shell_profile"
  path_status=updated
}

print_path_guidance() {
  case "$path_status" in
    active) ;;
    updated)
      say "Added Oblive to PATH in $shell_profile."
      say "Open a new terminal or run: $path_line"
      ;;
    configured)
      say "Oblive is already configured in $shell_profile."
      say "To use it in this shell, run: $path_line"
      ;;
    skipped) say "To use Oblive in this shell, run: $path_line" ;;
  esac
}

mkdir -p "$config_home" "$bin_dir" "$(dirname "$install_dir")"
[ ! -e "$cli_link" ] || [ -L "$cli_link" ] || fail "$cli_link already exists and is not a symlink"

if [ -x "$install_dir/oblive" ] && [ -f "$install_dir/VERSION" ]; then
  if [ "$instance" != default ]; then
    [ "$("$install_dir/oblive" --instance-format 2>/dev/null || true)" = 1 ] ||
      fail "this release does not support named instances"
  fi
  printf '%s\n' "$download_base_url" > "$install_dir/.download-base-url"
  printf '%s\n' "$channel" > "$install_dir/.channel"
  if [ "$channel" = latest ]; then
    OBLIVE_HOME=$install_dir "$install_dir/oblive" update --latest
  else
    OBLIVE_HOME=$install_dir "$install_dir/oblive" update --version "$requested_version"
  fi
else
  [ ! -e "$install_dir" ] || fail "$install_dir exists but is not a valid Oblive installation"
  if [ "$channel" = latest ]; then
    curl --fail --location --silent --show-error --retry 3 \
      "$download_base_url/releases/latest" --output "$temporary_root/latest"
    release_version=$(normalize_version "$(sed -n '1p' "$temporary_root/latest")")
  else
    release_version=$requested_version
  fi
  release_url="$download_base_url/releases/$release_version"

  curl --fail --location --silent --show-error --retry 3 "$release_url/$asset_name" \
    --output "$temporary_root/$asset_name"
  curl --fail --location --silent --show-error --retry 3 "$release_url/$asset_name.sha256" \
    --output "$temporary_root/$asset_name.sha256"

  expected_checksum=$(awk 'NR == 1 { print $1 }' "$temporary_root/$asset_name.sha256")
  case "$expected_checksum" in *[!0-9a-f]* | "") fail "the release checksum is invalid" ;; esac
  [ "$(printf '%s' "$expected_checksum" | wc -c | tr -d ' ')" = 64 ] || fail "the release checksum is invalid"
  if command -v sha256sum >/dev/null 2>&1; then
    actual_checksum=$(sha256sum "$temporary_root/$asset_name" | awk '{ print $1 }')
  else
    actual_checksum=$(shasum -a 256 "$temporary_root/$asset_name" | awk '{ print $1 }')
  fi
  [ "$actual_checksum" = "$expected_checksum" ] || fail "the release checksum did not match"

  tar -tzf "$temporary_root/$asset_name" | while IFS= read -r entry; do
    case "$entry" in /* | ../* | */../* | */.. | *\\*) exit 1 ;; esac
  done || fail "the release archive contains an unsafe path"
  tar -tvzf "$temporary_root/$asset_name" | awk 'substr($1, 1, 1) !~ /^[-d]$/ { exit 1 }' ||
    fail "the release archive contains a link or special file"

  extract_dir="$temporary_root/extracted"
  mkdir "$extract_dir"
  tar -xzf "$temporary_root/$asset_name" -C "$extract_dir"
  bundled_version=$(sed -n '1p' "$extract_dir/VERSION")
  normalize_version "$bundled_version" >/dev/null
  [ "$bundled_version" = "$release_version" ] || fail "the release bundle does not match $release_version"

  if [ "$instance" != default ] || [ -n "$port_base" ]; then
    [ "$("$extract_dir/oblive" --instance-format 2>/dev/null || true)" = 1 ] ||
      fail "this release does not support named instances or --port-base; select a newer release"
  fi

  mv "$extract_dir" "$install_dir"
  chmod 0755 "$install_dir/oblive"
  chmod 0644 "$install_dir/local-stack.schema.json"
  printf '%s\n' "$download_base_url" > "$install_dir/.download-base-url"
  printf '%s\n' "$channel" > "$install_dir/.channel"
fi

printf '%s\n' "$instance" > "$install_dir/.instance"
printf '%s\n' "$cli_link" > "$install_dir/.cli-link"
if [ -n "$port_base" ]; then
  [ "$("$install_dir/oblive" --instance-format 2>/dev/null || true)" = 1 ] ||
    fail "this release does not support --port-base"
  OBLIVE_HOME=$install_dir "$install_dir/oblive" _init "$auth_mode" "$port_base"
else
  OBLIVE_HOME=$install_dir "$install_dir/oblive" _init "$auth_mode"
fi
mkdir -p "$(dirname "$install_pointer")"
printf '%s\n' "$install_dir" > "$install_pointer"
# Keep an instance-aware dispatcher when installing a legacy default release.
if [ "$("$install_dir/oblive" --instance-format 2>/dev/null || true)" = 1 ] ||
  [ ! -x "$cli_link" ] || [ "$("$cli_link" --instance-format 2>/dev/null || true)" != 1 ]; then
  ln -sfn "$install_dir/oblive" "$cli_link"
fi
configure_path

say "Installed Oblive $(sed -n '1p' "$install_dir/VERSION") in $install_dir."
print_path_guidance
if [ "$start_after_install" = true ]; then
  OBLIVE_HOME=$install_dir "$install_dir/oblive" start
else
  say "Configuration: $install_dir/local-stack.json"
  if [ "$instance" = default ]; then say "Start later with: oblive start"
  else say "Start later with: oblive --instance $instance start"; fi
fi
