#!/usr/bin/env bash

set -euo pipefail
IFS=$'\n\t'

readonly DEFAULT_REPO_URL="https://github.com/estimand-app/estimand.git"
readonly DEFAULT_INSTALL_DIR="${HOME}/.estimand/estimand"
readonly DEFAULT_REF="main"

usage() {
  cat <<'EOF'
Usage: install.sh [options]

Options:
  -d, --dir PATH        Install/update destination directory (default: ~/.estimand/estimand)
  -r, --ref REF         Git ref to check out (default: main)
  -u, --repo URL        Git repository URL (default: https://github.com/estimand-app/estimand.git)
  -c, --compose PATH    Explicit compose file path to start
  --skip-start          Clone/update checkout and write .env.self-host but do not start stack
  -h, --help            Show this help and exit
EOF
}

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

fatal() {
  printf '[estimand-install] ERROR: %s\n' "$*" >&2
  exit 1
}

require_command() {
  if ! command -v "$1" >/dev/null 2>&1; then
    fatal "required command not found: $1"
  fi
}

clone_or_update_checkout() {
  local repo_dir="$1"
  local repo_url="$2"
  local ref="$3"

  if [ ! -e "$repo_dir" ]; then
    log "Installing into $repo_dir"
    require_command git
    git clone --depth 1 --branch "$ref" "$repo_url" "$repo_dir"
    return
  fi

  if [ -e "$repo_dir/.git" ] && [ ! -d "$repo_dir/.git" ]; then
    fatal "install path exists and is not a git checkout: $repo_dir"
  fi

  if [ ! -d "$repo_dir/.git" ]; then
    if [ -n "$(ls -A "$repo_dir")" ]; then
      fatal "target path exists but is not a git checkout and is not empty: $repo_dir"
    fi

    log "Directory is empty; cloning fresh checkout into $repo_dir"
    require_command git
    git clone --depth 1 --branch "$ref" "$repo_url" "$repo_dir"
    return
  fi

  local origin
  origin="$(git -C "$repo_dir" remote get-url origin 2>/dev/null || true)"
  if [ -z "$origin" ]; then
    fatal "existing checkout has no 'origin' remote: $repo_dir"
  fi

  require_command git
  log "Updating existing checkout in $repo_dir"
  git -C "$repo_dir" fetch --all --prune --depth 1

  if git -C "$repo_dir" show-ref --verify --quiet "refs/remotes/origin/${ref}"; then
    git -C "$repo_dir" checkout -B "$ref" "origin/${ref}"
    git -C "$repo_dir" reset --hard "origin/${ref}"
  else
    git -C "$repo_dir" checkout "$ref"
    git -C "$repo_dir" pull --ff-only
  fi
}

ensure_env_file() {
  local repo_dir="$1"
  local env_path="$repo_dir/.env.self-host"
  local example_path="$repo_dir/.env.self-host.example"

  if [ -f "$env_path" ]; then
    return
  fi

  if [ -f "$example_path" ]; then
    cp -- "$example_path" "$env_path"
    chmod 600 "$env_path"
    log "Created .env.self-host from .env.self-host.example"
    return
  fi

  cat <<'EOF' > "$env_path"
DEBUG=true
DJANGO_SECRET_KEY=change-me
ALLOWED_HOSTS=*
DATABASE_URL=postgres://estimand:estimand@postgres:5432/estimand
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
API_KEY_PEPPER=change-me
CLICKHOUSE_HOST=clickhouse
CLICKHOUSE_PORT=8123
CLICKHOUSE_HTTP_PORT=8123
CLICKHOUSE_DATABASE=estimand_events
CLICKHOUSE_TABLE=events
CLICKHOUSE_USER=
CLICKHOUSE_PASSWORD=
CLICKHOUSE_SECURE=false
CLICKHOUSE_INSERT_TIMEOUT_SECONDS=5
EOF
  chmod 600 "$env_path"
  log "Created fallback .env.self-host template"
}

resolve_compose_file() {
  local repo_dir="$1"
  local explicit="$2"
  local candidate
  local resolved_explicit

  if [ -n "$explicit" ]; then
    if [ -f "$explicit" ]; then
      resolved_explicit="$explicit"
    elif [ -f "$repo_dir/$explicit" ]; then
      resolved_explicit="$repo_dir/$explicit"
    else
      fatal "compose file not found: $explicit"
    fi

    printf '%s\n' "$resolved_explicit"
    return 0
  fi

  local candidates=(
    "$repo_dir/docker-compose.yml"
    "$repo_dir/docker-compose.yaml"
    "$repo_dir/compose.yml"
    "$repo_dir/compose.yaml"
    "$repo_dir/docs/self-host/docker-compose.yml"
    "$repo_dir/docs/self-host/docker-compose.yaml"
    "$repo_dir/docs/self-host/compose.yml"
    "$repo_dir/docs/self-host/compose.yaml"
  )

  for candidate in "${candidates[@]}"; do
    if [ -f "$candidate" ]; then
      printf '%s\n' "$candidate"
      return 0
    fi
  done

  fatal "no compose file found; expected one of: docker-compose.yml/.yaml, compose.yml/.yaml, docs/self-host/*."
}

start_stack() {
  local compose_path="$1"
  local env_path="$2"
  local compose_dir
  compose_dir="$(cd "$(dirname "$compose_path")" && pwd -P)"

  require_command docker
  local compose_cmd=()
  if docker compose version >/dev/null 2>&1; then
    compose_cmd=(docker compose --env-file "$env_path" -f "$compose_path")
  elif command -v docker-compose >/dev/null 2>&1; then
    compose_cmd=(docker-compose --env-file "$env_path" -f "$compose_path")
  else
    fatal "docker compose v2 or docker-compose v1 is required to start the stack"
  fi

  log "Starting self-host stack from $(basename "$compose_path")"
  (cd "$compose_dir" && "${compose_cmd[@]}" up -d --remove-orphans)
}

main() {
  local install_dir="${DEFAULT_INSTALL_DIR}"
  local repo_url="${DEFAULT_REPO_URL}"
  local ref="${DEFAULT_REF}"
  local compose_file=""
  local skip_start="false"

  while (( $# > 0 )); do
    case "$1" in
      -d|--dir)
        if (( $# < 2 )); then
          fatal "missing value for --dir"
        fi
        install_dir="${2:-}"
        shift 2
        ;;
      -r|--ref)
        if (( $# < 2 )); then
          fatal "missing value for --ref"
        fi
        ref="${2:-}"
        shift 2
        ;;
      -u|--repo)
        if (( $# < 2 )); then
          fatal "missing value for --repo"
        fi
        repo_url="${2:-}"
        shift 2
        ;;
      -c|--compose)
        if (( $# < 2 )); then
          fatal "missing value for --compose"
        fi
        compose_file="${2:-}"
        shift 2
        ;;
      --skip-start)
        skip_start="true"
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        fatal "unknown argument: $1"
        ;;
    esac
  done

  if [ -z "$install_dir" ] || [ -z "$repo_url" ] || [ -z "$ref" ]; then
    fatal "required parameter is empty"
  fi

  clone_or_update_checkout "$install_dir" "$repo_url" "$ref"
  ensure_env_file "$install_dir"

  if [ "$skip_start" = "true" ]; then
    log "Skip start requested; installation complete"
    return 0
  fi

  local resolved_compose_file
  resolved_compose_file="$(resolve_compose_file "$install_dir" "$compose_file")"
  start_stack "$resolved_compose_file" "$install_dir/.env.self-host"
}

main "$@"
