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

APP="executor"
REPO="${EXECUTOR_REPO:-RhysSullivan/executor}"
INSTALL_DIR="${EXECUTOR_INSTALL_DIR:-$HOME/.executor/bin}"
WEB_INSTALL_DIR="${EXECUTOR_WEB_INSTALL_DIR:-$HOME/.executor/runtime/web}"
RUNTIME_DIR="${EXECUTOR_RUNTIME_DIR:-$HOME/.executor/runtime}"
REQUESTED_VERSION="${VERSION:-}"
RESOLVED_VERSION=""
BINARY_PATH=""
NO_MODIFY_PATH="0"
STAR_PROMPT="1"
SCRIPT_FILE="${BASH_SOURCE[0]:-$0}"
SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_FILE}")" >/dev/null 2>&1 && pwd)"
LOCAL_BINARY_CANDIDATE="$SCRIPT_DIR/dist/executor"
LOCAL_RELEASE_CANDIDATE="$SCRIPT_DIR/dist/release"
CHECKSUMS_FILE=""
CHECKSUMS_FETCHED="0"

RED='\033[0;31m'
MUTED='\033[0;2m'
NC='\033[0m'

usage() {
  cat <<EOF
Executor installer

Usage:
  curl -fsSL https://executor.sh/install | bash

Options:
  -v, --version <version>  Install specific release tag (with or without leading v)
  -b, --binary <path>      Install from a local prebuilt binary
  --no-star-prompt         Do not show GitHub star prompt
  --no-modify-path         Do not append executor bin directory to shell rc
  -h, --help               Show this help

Environment:
  EXECUTOR_REPO            GitHub repository to install from (default: RhysSullivan/executor)
  EXECUTOR_INSTALL_DIR     Install directory (default: ~/.executor/bin)
  EXECUTOR_WEB_INSTALL_DIR Web bundle directory (default: ~/.executor/runtime/web)
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help)
      usage
      exit 0
      ;;
    -v|--version)
      REQUESTED_VERSION="${2:-}"
      if [[ -z "$REQUESTED_VERSION" ]]; then
        echo -e "${RED}Missing value for --version${NC}"
        exit 1
      fi
      shift 2
      ;;
    -b|--binary)
      BINARY_PATH="${2:-}"
      if [[ -z "$BINARY_PATH" ]]; then
        echo -e "${RED}Missing value for --binary${NC}"
        exit 1
      fi
      shift 2
      ;;
    --no-modify-path)
      NO_MODIFY_PATH="1"
      shift
      ;;
    --no-star-prompt)
      STAR_PROMPT="0"
      shift
      ;;
    *)
      echo -e "${RED}Unknown option: $1${NC}"
      exit 1
      ;;
  esac
done

if [[ -n "$REQUESTED_VERSION" ]]; then
  RESOLVED_VERSION="${REQUESTED_VERSION#v}"
fi

raw_os="$(uname -s)"
case "$raw_os" in
  Linux*) os="linux" ;;
  Darwin*) os="darwin" ;;
  *)
    echo -e "${RED}Unsupported OS: ${raw_os}${NC}"
    exit 1
    ;;
esac

raw_arch="$(uname -m)"
case "$raw_arch" in
  x86_64|amd64) arch="x64" ;;
  arm64|aarch64) arch="arm64" ;;
  *)
    echo -e "${RED}Unsupported architecture: ${raw_arch}${NC}"
    exit 1
    ;;
esac

archive_ext="tar.gz"

filename="${APP}-${os}-${arch}.${archive_ext}"
runtime_filename="executor-runtime-${os}-${arch}.tar.gz"
LOCAL_RUNTIME_CANDIDATE="$LOCAL_RELEASE_CANDIDATE/$runtime_filename"
BACKEND_PORT="${EXECUTOR_BACKEND_PORT:-5410}"
WEB_PORT="${EXECUTOR_WEB_PORT:-5312}"
SERVICE_DIR="$RUNTIME_DIR/services"
LOG_DIR="$RUNTIME_DIR/logs"
BACKEND_PID_FILE="$SERVICE_DIR/backend.pid"
WEB_PID_FILE="$SERVICE_DIR/web.pid"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
mkdir -p "$INSTALL_DIR"
mkdir -p "$WEB_INSTALL_DIR"

require_command() {
  local command_name="$1"
  local install_hint="$2"
  if command -v "$command_name" >/dev/null 2>&1; then
    return
  fi

  echo -e "${RED}Missing required dependency: ${command_name}${NC}"
  echo "Install hint: ${install_hint}"
  exit 1
}

sha256_of_file() {
  local target="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$target" | awk '{print $1}'
    return
  fi

  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$target" | awk '{print $1}'
    return
  fi

  echo -e "${RED}Missing checksum utility (sha256sum or shasum)${NC}"
  exit 1
}

ensure_install_prerequisites() {
  require_command "curl" "Install curl from your OS package manager."
  require_command "tar" "Install tar from your OS package manager."
  if command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1; then
    return
  fi

  echo -e "${RED}Missing required dependency: sha256sum or shasum${NC}"
  echo "Install hint: coreutils (sha256sum) or perl-Digest-SHA tools (shasum)."
  exit 1
}

download_release_checksums() {
  if [[ "$CHECKSUMS_FETCHED" == "1" ]]; then
    return
  fi

  CHECKSUMS_FETCHED="1"
  local checksums_url
  if [[ -z "$REQUESTED_VERSION" ]]; then
    checksums_url="https://github.com/${REPO}/releases/latest/download/checksums.txt"
  else
    checksums_url="https://github.com/${REPO}/releases/download/v${RESOLVED_VERSION}/checksums.txt"
  fi

  local destination="$WORK_DIR/checksums.txt"
  if curl -fsSL "$checksums_url" -o "$destination"; then
    CHECKSUMS_FILE="$destination"
  fi
}

verify_release_checksum_if_available() {
  local downloaded_file="$1"
  local artifact_name="$2"
  download_release_checksums

  if [[ -z "$CHECKSUMS_FILE" || ! -f "$CHECKSUMS_FILE" ]]; then
    return
  fi

  local expected
  expected="$(grep -E "[[:xdigit:]]+[[:space:]]+${artifact_name}$" "$CHECKSUMS_FILE" | awk '{print $1}' | head -n1)"
  if [[ -z "$expected" ]]; then
    return
  fi

  local actual
  actual="$(sha256_of_file "$downloaded_file")"
  if [[ "$actual" != "$expected" ]]; then
    echo -e "${RED}Checksum mismatch for ${artifact_name}${NC}"
    echo "  expected: ${expected}"
    echo "  actual:   ${actual}"
    exit 1
  fi
}

ensure_install_prerequisites

install_local_binary() {
  local src="$1"
  local tmp_target
  if [[ ! -f "$src" ]]; then
    echo -e "${RED}Local binary not found: ${src}${NC}"
    exit 1
  fi
  tmp_target="$INSTALL_DIR/.${APP}.tmp.$$"
  cp "$src" "$tmp_target"
  chmod 755 "$tmp_target"
  mv -f "$tmp_target" "$INSTALL_DIR/$APP"
}

install_runtime_bundle_from_archive() {
  local archive_path="$1"
  local tmp_runtime_dir
  tmp_runtime_dir="$(mktemp -d)"

  if ! tar -xzf "$archive_path" -C "$tmp_runtime_dir"; then
    rm -rf "$tmp_runtime_dir"
    echo -e "${RED}Failed extracting runtime archive${NC}"
    exit 1
  fi

  if [[ ! -f "$tmp_runtime_dir/convex-backend/convex-local-backend" ]]; then
    rm -rf "$tmp_runtime_dir"
    echo -e "${RED}Runtime archive missing managed backend binary${NC}"
    exit 1
  fi

  if [[ ! -f "$tmp_runtime_dir/web/server.js" ]]; then
    rm -rf "$tmp_runtime_dir"
    echo -e "${RED}Runtime archive missing web server entry${NC}"
    exit 1
  fi

  mkdir -p "$RUNTIME_DIR"

  rm -rf "$RUNTIME_DIR/convex-backend"
  cp -R "$tmp_runtime_dir/convex-backend" "$RUNTIME_DIR/convex-backend"

  rm -rf "$RUNTIME_DIR/web"
  cp -R "$tmp_runtime_dir/web" "$RUNTIME_DIR/web"

  if [[ -d "$tmp_runtime_dir/npm" ]]; then
    rm -rf "$RUNTIME_DIR/npm"
    cp -R "$tmp_runtime_dir/npm" "$RUNTIME_DIR/npm"
  fi

  shopt -s nullglob
  local extracted_node_dirs=("$tmp_runtime_dir"/node-v*)
  shopt -u nullglob
  if [[ ${#extracted_node_dirs[@]} -eq 0 ]]; then
    rm -rf "$tmp_runtime_dir"
    echo -e "${RED}Runtime archive missing managed node runtime${NC}"
    exit 1
  fi

  rm -rf "$RUNTIME_DIR"/node-v*
  local node_dir
  for node_dir in "${extracted_node_dirs[@]}"; do
    cp -R "$node_dir" "$RUNTIME_DIR/"
  done

  local keep_existing_data="0"
  if [[ -d "$RUNTIME_DIR/convex-data" ]]; then
    shopt -s nullglob dotglob
    local existing_data=("$RUNTIME_DIR/convex-data"/*)
    shopt -u nullglob dotglob
    if [[ ${#existing_data[@]} -gt 0 ]]; then
      keep_existing_data="1"
    fi
  fi

  if [[ "$keep_existing_data" == "1" ]]; then
    echo -e "${MUTED}Preserving existing local database and storage${NC}"
  else
    rm -rf "$RUNTIME_DIR/convex-data"
    cp -R "$tmp_runtime_dir/convex-data" "$RUNTIME_DIR/convex-data"
  fi

  if [[ "$keep_existing_data" == "1" && -f "$RUNTIME_DIR/managed-anonymous-auth.json" ]]; then
    :
  elif [[ -f "$tmp_runtime_dir/managed-anonymous-auth.json" ]]; then
    cp "$tmp_runtime_dir/managed-anonymous-auth.json" "$RUNTIME_DIR/managed-anonymous-auth.json"
    chmod 600 "$RUNTIME_DIR/managed-anonymous-auth.json" || true
  fi

  if [[ "$WEB_INSTALL_DIR" != "$RUNTIME_DIR/web" ]]; then
    mkdir -p "$(dirname "$WEB_INSTALL_DIR")"
    rm -rf "$WEB_INSTALL_DIR"
    cp -R "$RUNTIME_DIR/web" "$WEB_INSTALL_DIR"
  fi

  rm -rf "$tmp_runtime_dir"
}

install_runtime_bundle() {
  local runtime_url
  local temp_archive="$WORK_DIR/$runtime_filename"

  if [[ -z "$REQUESTED_VERSION" ]]; then
    runtime_url="https://github.com/${REPO}/releases/latest/download/${runtime_filename}"
  else
    runtime_url="https://github.com/${REPO}/releases/download/v${RESOLVED_VERSION}/${runtime_filename}"
  fi

  if curl -fsSL "$runtime_url" -o "$temp_archive"; then
    verify_release_checksum_if_available "$temp_archive" "$runtime_filename"
    echo -e "${MUTED}Installing runtime bundle${NC} ${runtime_filename}"
    install_runtime_bundle_from_archive "$temp_archive"
    return
  fi

  if [[ -f "$LOCAL_RUNTIME_CANDIDATE" ]]; then
    echo -e "${MUTED}Runtime bundle release asset not found; using local archive at ${LOCAL_RUNTIME_CANDIDATE}${NC}"
    install_runtime_bundle_from_archive "$LOCAL_RUNTIME_CANDIDATE"
    return
  fi

  echo -e "${RED}Runtime bundle was not installed (missing release asset ${runtime_filename})${NC}"
  echo ""
  echo "To build and install it locally:"
  echo "  bun run --cwd executor build:binary"
  echo "  bash executor/install --binary executor/dist/executor --no-modify-path"
  exit 1
}

if [[ -n "$BINARY_PATH" ]]; then
  echo -e "${MUTED}Installing ${APP}${NC} from local binary: $BINARY_PATH"
  install_local_binary "$BINARY_PATH"
else
  if [[ -z "$REQUESTED_VERSION" ]]; then
    url="https://github.com/${REPO}/releases/latest/download/${filename}"
    release_json="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null || true)"
    version="$(printf "%s" "$release_json" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')"
    if [[ -z "$version" ]]; then
      version="latest"
    fi
    RESOLVED_VERSION="${version#v}"
  else
    version="${REQUESTED_VERSION#v}"
    RESOLVED_VERSION="$version"
    url="https://github.com/${REPO}/releases/download/v${version}/${filename}"
  fi

  tmp_dir="$WORK_DIR"

  echo -e "${MUTED}Installing ${APP}${NC} ${version:-latest}"
  if ! curl -fsSL "$url" -o "$tmp_dir/$filename"; then
    if [[ -f "$LOCAL_BINARY_CANDIDATE" ]]; then
      echo -e "${MUTED}Release asset not found; using local binary at ${LOCAL_BINARY_CANDIDATE}${NC}"
      install_local_binary "$LOCAL_BINARY_CANDIDATE"
    else
      echo -e "${RED}Could not download release asset: ${url}${NC}"
      echo ""
      echo "If you are developing locally, build and install from source:"
      echo "  bun run --cwd executor build:binary"
      echo "  bash executor/install --binary executor/dist/executor"
      exit 1
    fi
  else
    verify_release_checksum_if_available "$tmp_dir/$filename" "$filename"
    tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"

    if [[ ! -f "$tmp_dir/$APP" ]]; then
      echo -e "${RED}Archive did not contain expected binary '${APP}'${NC}"
      exit 1
    fi

    mv "$tmp_dir/$APP" "$INSTALL_DIR/$APP"
    chmod 755 "$INSTALL_DIR/$APP"
  fi
fi

show_star_prompt() {
  local repo_url
  local star_cmd
  repo_url="https://github.com/${REPO}"
  star_cmd="gh api --method PUT /user/starred/${REPO}"

  if [[ "$STAR_PROMPT" != "1" ]]; then
    return
  fi

  if command -v gh >/dev/null 2>&1; then
    echo "If Executor helps, star with GitHub CLI ⭐:"
    echo "  ${star_cmd}"
  else
    echo "If Executor helps, please star: ${repo_url}"
  fi
}

is_pid_running() {
  local pid="$1"
  if [[ -z "$pid" ]]; then
    return 1
  fi
  kill -0 "$pid" >/dev/null 2>&1
}

cleanup_pid_file() {
  local pid_file="$1"
  if [[ ! -f "$pid_file" ]]; then
    return
  fi

  local pid
  pid="$(cat "$pid_file" 2>/dev/null || true)"
  if ! is_pid_running "$pid"; then
    rm -f "$pid_file"
  fi
}

wait_for_http() {
  local url="$1"
  local attempts="${2:-60}"
  local index=0

  while (( index < attempts )); do
    if curl -fsS --max-time 1 "$url" >/dev/null 2>&1; then
      return 0
    fi
    index=$((index + 1))
    sleep 0.25
  done

  return 1
}

stop_pid_file() {
  local pid_file="$1"
  if [[ ! -f "$pid_file" ]]; then
    return
  fi

  local pid
  pid="$(cat "$pid_file" 2>/dev/null || true)"
  if [[ -n "$pid" ]] && is_pid_running "$pid"; then
    kill "$pid" >/dev/null 2>&1 || true
    sleep 1
    if is_pid_running "$pid"; then
      kill -9 "$pid" >/dev/null 2>&1 || true
    fi
  fi

  rm -f "$pid_file"
}

stop_existing_services() {
  local likely_running=0

  if wait_for_http "http://127.0.0.1:${BACKEND_PORT}/version" 2; then
    likely_running=1
  fi
  if wait_for_http "http://127.0.0.1:${WEB_PORT}/" 2; then
    likely_running=1
  fi
  if [[ -f "$BACKEND_PID_FILE" || -f "$WEB_PID_FILE" ]]; then
    likely_running=1
  fi

  if (( likely_running == 0 )); then
    return
  fi

  echo -e "${MUTED}Stopping existing managed services before update${NC}"
  "$INSTALL_DIR/$APP" down >/dev/null 2>&1 || true
  stop_pid_file "$WEB_PID_FILE"
  stop_pid_file "$BACKEND_PID_FILE"
}

start_background_services() {
  mkdir -p "$SERVICE_DIR"
  mkdir -p "$LOG_DIR"

  cleanup_pid_file "$BACKEND_PID_FILE"
  cleanup_pid_file "$WEB_PID_FILE"

  if wait_for_http "http://127.0.0.1:${BACKEND_PORT}/version" 2; then
    echo -e "${MUTED}Managed backend already running${NC} http://127.0.0.1:${BACKEND_PORT}"
  elif [[ -f "$BACKEND_PID_FILE" ]]; then
    echo -e "${MUTED}Managed backend already tracked${NC} (pid $(cat "$BACKEND_PID_FILE"))"
  else
    nohup "$INSTALL_DIR/$APP" up --disable-beacon >"$LOG_DIR/backend.log" 2>&1 &
    echo "$!" > "$BACKEND_PID_FILE"
    if wait_for_http "http://127.0.0.1:${BACKEND_PORT}/version"; then
      echo -e "${MUTED}Started managed backend${NC} http://127.0.0.1:${BACKEND_PORT}"
    else
      echo -e "${MUTED}Managed backend is still initializing${NC} (continuing readiness checks)"
    fi
  fi

  if wait_for_http "http://127.0.0.1:${WEB_PORT}/" 2; then
    echo -e "${MUTED}Web UI already running${NC} http://127.0.0.1:${WEB_PORT}"
  elif [[ -f "$WEB_PID_FILE" ]]; then
    echo -e "${MUTED}Web UI already tracked${NC} (pid $(cat "$WEB_PID_FILE"))"
  else
    nohup "$INSTALL_DIR/$APP" web >"$LOG_DIR/web.log" 2>&1 &
    echo "$!" > "$WEB_PID_FILE"
    if wait_for_http "http://127.0.0.1:${WEB_PORT}/"; then
      echo -e "${MUTED}Started web UI${NC} http://127.0.0.1:${WEB_PORT}"
    else
      echo -e "${MUTED}Web UI is still initializing${NC} (continuing readiness checks)"
    fi
  fi
}

wait_for_executor_ready() {
  local max_seconds="${1:-90}"
  local started now elapsed
  local doctor_interval=10
  local last_doctor_at=0
  local backend_up=0
  local web_up=0
  local doctor_ok=1

  started="$(date +%s)"
  echo -e "${MUTED}Waiting for services to become healthy (up to ${max_seconds}s)${NC}"

  while true; do
    if wait_for_http "http://127.0.0.1:${BACKEND_PORT}/version" 1; then
      backend_up=1
    fi
    if wait_for_http "http://127.0.0.1:${WEB_PORT}/" 1; then
      web_up=1
    fi

    now="$(date +%s)"
    elapsed=$((now - started))

    if (( backend_up == 1 && web_up == 1 )); then
      if (( now - last_doctor_at >= doctor_interval )); then
        if "$INSTALL_DIR/$APP" doctor --runtime-only >/dev/null 2>&1; then
          echo -e "${MUTED}Executor services healthy${NC}"
          return 0
        fi
        last_doctor_at="$(date +%s)"

        now="$(date +%s)"
        elapsed=$((now - started))
        if (( elapsed >= max_seconds )); then
          break
        fi
      fi
      doctor_ok=0
    fi

    if (( elapsed >= max_seconds )); then
      break
    fi

    if (( elapsed > 0 && elapsed % 10 == 0 )); then
      if (( backend_up == 0 || web_up == 0 )); then
        local backend_state="starting"
        local web_state="starting"
        if (( backend_up == 1 )); then
          backend_state="running"
        fi
        if (( web_up == 1 )); then
          web_state="running"
        fi
        echo -e "${MUTED}Still starting... backend=${backend_state} web=${web_state}${NC}"
      elif (( doctor_ok == 0 )); then
        echo -e "${MUTED}Services up; finalizing health checks...${NC}"
      fi
    fi

    sleep 0.5
  done

  echo -e "${RED}Executor services are running but not fully ready yet.${NC}"
  echo "  Check: $INSTALL_DIR/$APP doctor --verbose"
  if [[ -f "$LOG_DIR/backend.log" ]]; then
    echo "  Backend log: $LOG_DIR/backend.log"
  fi
  if [[ -f "$LOG_DIR/web.log" ]]; then
    echo "  Web log: $LOG_DIR/web.log"
  fi
  return 1
}

stop_existing_services
install_runtime_bundle

if [[ "$NO_MODIFY_PATH" != "1" ]]; then
  shell_name="$(basename "${SHELL:-}")"
  case "$shell_name" in
    zsh) shell_rc="$HOME/.zshrc" ;;
    bash) shell_rc="$HOME/.bashrc" ;;
    *) shell_rc="$HOME/.profile" ;;
  esac

  export_line="export PATH=${INSTALL_DIR}:\$PATH"
  if [[ -f "$shell_rc" ]]; then
    if ! grep -Fxq "$export_line" "$shell_rc"; then
      printf "\n# executor\n%s\n" "$export_line" >> "$shell_rc"
    fi
  fi
fi

start_background_services
runtime_ready=0
if wait_for_executor_ready; then
  runtime_ready=1
fi

if (( runtime_ready == 0 )); then
  echo -e "${RED}Skipping final runtime confirmation because services are not healthy yet.${NC}"
fi

echo ""
echo "Installed to: $INSTALL_DIR/$APP"
if [[ -n "$RESOLVED_VERSION" ]]; then
  if [[ "$RESOLVED_VERSION" == "latest" ]]; then
    echo "Version: latest"
  else
    echo "Version: v$RESOLVED_VERSION"
  fi
elif [[ -n "$BINARY_PATH" || -f "$LOCAL_BINARY_CANDIDATE" ]]; then
  echo "Version: local build"
fi
if [[ -f "$WEB_INSTALL_DIR/server.js" ]]; then
  echo "Runtime bundle installed to: $RUNTIME_DIR"
fi
echo "Dashboard: http://localhost:${WEB_PORT}"
echo "Use:"
echo "  executor doctor            # friendly health check"
echo "  executor upgrade           # update to latest release"
echo "  executor down              # stop background services"
echo "  executor up                # start backend manually"
echo "  executor web               # start web manually"
echo ""
echo "To uninstall later:"
echo "  executor uninstall --yes"
echo "  bash executor/uninstall --yes"
echo ""
show_star_prompt
