#!/usr/bin/env bash

# Require bash for /dev/tcp and other bash-specific features
[ -n "$BASH_VERSION" ] || { echo "This installer requires bash"; exit 1; }

# Guard shell options when running under tests
if [[ -z "${GOODMEM_TEST_SOURCING:-}" ]]; then
    set -Eeuo pipefail
fi

# Cleanup function for proper error handling
cleanup() {
    local exit_code=$?
    [[ ${#temp_files[@]} -gt 0 ]] && rm -f -- "${temp_files[@]}"
    [[ ${#temp_dirs[@]} -gt 0 ]] && rm -rf -- "${temp_dirs[@]}"
    return $exit_code
}

# Set up cleanup trap (only when running directly, not when sourced)
if [[ -z "${GOODMEM_TEST_SOURCING:-}" ]]; then
    trap cleanup EXIT
fi

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Configurable base URL
: "${BASE_URL:=https://get.goodmem.ai}"

# Installation mode flags
INSTALL_MODE=""  # Can be "prod", "debug", or empty (interactive)
SKIP_OPENAI_SETUP=false
DEBUG_OUTPUT=false

# Database configuration flags
USE_REMOTE_DB=false
USE_LOCAL_DB=false
DB_URL_DSN=""      # libpq DSN for preflight checks (psql)
DB_URL_JDBC=""     # JDBC URL for server container
DB_HOST=""
DB_PORT="5432"
DB_USER=""
DB_PASSWORD=""
DB_NAME=""
DB_SSL_MODE="require"

# Track temporary files and directories for cleanup
temp_files=()
temp_dirs=()

# Parse command line arguments
parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --prod-install)
                INSTALL_MODE="prod"
                shift
                ;;
            --debug-install)
                INSTALL_MODE="debug"
                shift
                ;;
            --no-openai-embedder-registration)
                SKIP_OPENAI_SETUP=true
                shift
                ;;
            --debug)
                DEBUG_OUTPUT=true
                shift
                ;;
            --local-db)
                USE_LOCAL_DB=true
                shift
                ;;
            --remote-db|--db-url)
                if [[ "$1" == "--db-url" ]]; then
                    shift
                    if [[ $# -eq 0 || "$1" == --* ]]; then
                        log_error "--db-url requires a PostgreSQL DSN"
                        exit 1
                    fi
                    if ! process_database_dsn "$1"; then
                        exit 1
                    fi
                fi
                USE_REMOTE_DB=true
                shift
                ;;
            --db-host)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-host requires a hostname or IP address"
                    exit 1
                fi
                DB_HOST="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --db-port)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-port requires a port number"
                    exit 1
                fi
                DB_PORT="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --db-user)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-user requires a username"
                    exit 1
                fi
                DB_USER="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --db-password)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-password requires a password"
                    exit 1
                fi
                DB_PASSWORD="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --db-name)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-name requires a database name"
                    exit 1
                fi
                DB_NAME="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --db-ssl-mode)
                shift
                if [[ $# -eq 0 || "$1" == --* ]]; then
                    log_error "--db-ssl-mode requires an SSL mode (disable, allow, prefer, require, verify-ca, verify-full)"
                    exit 1
                fi
                DB_SSL_MODE="$1"
                USE_REMOTE_DB=true
                shift
                ;;
            --help|-h)
                show_help
                exit 0
                ;;
            *)
                log_error "Unknown option: $1"
                show_help
                exit 1
                ;;
        esac
    done
}

# Show help message
show_help() {
    echo "GoodMem Installation Script"
    echo ""
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Installation Mode Options:"
    echo "  --prod-install                     Install in production mode (database port not exposed)"
    echo "  --debug-install                    Install in debug mode (with additional debugging features)"
    echo "  --no-openai-embedder-registration  Skip OpenAI embedder setup (unattended mode)"
    echo ""
    echo "Database Configuration Options:"
    echo "  --local-db                         Use a local PostgreSQL database (Docker container)"
    echo "  --remote-db                        Use a remote PostgreSQL database"
    echo "  --db-url DSN                       Postgres DSN (libpq)."
    echo "                                     Example: postgresql://user:pa%40ss@host:5432/goodmem?sslmode=require"
    echo "                                     NOTE: Percent-encode reserved chars in credentials (@:/?&#%=)."
    echo "  --db-host HOST                     Database host"
    echo "  --db-port PORT                     Database port (default: 5432)"
    echo "  --db-user USER                     Database username"
    echo "  --db-password PASSWORD             Database password"
    echo "  --db-name DATABASE                 Database name"
    echo "  --db-ssl-mode MODE                 SSL mode: disable, allow, prefer, require, verify-full (default: require)"
    echo ""
    echo "General Options:"
    echo "  --debug                            Enable debug output"
    echo "  --help, -h                         Show this help message"
    echo ""
    echo "Examples:"
    echo "  $0                                 Interactive installation with mode selection"
    echo "  $0 --prod-install --local-db      Production installation with local database"
    echo ""
    echo "  # Remote DB via DSN (recommended for admins):"
    echo "  $0 --prod-install --remote-db \\"
    echo "     --db-url 'postgresql://gm:secret@db.example.com:5432/goodmem?sslmode=require'"
    echo ""
    echo "  # Remote DB via individual flags:"
    echo "  $0 --prod-install --remote-db \\"
    echo "     --db-host db.example.com --db-port 5432 \\"
    echo "     --db-name goodmem --db-user gm --db-password secret \\"
    echo "     --db-ssl-mode verify-full"
    echo ""
}

_supports_color() {
  # any terminal available? (stderr or /dev/tty), and not dumb, and NO_COLOR not set
  { [ -t 2 ] || [ -w /dev/tty ]; } && [ "${TERM:-}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]
}

# Helper functions
log_info() {
    printf "${BLUE}[INFO]${NC} %s\n" "$1"
}

log_success() {
    printf "${GREEN}[SUCCESS]${NC} %s\n" "$1"
}

log_warning() {
  local line
  if _supports_color; then
    line=$(printf '%b[WARNING]%b %s\n' "${YELLOW-}" "${NC-}" "${1-}")
  else
    line=$(printf '[WARNING] %s\n' "${1-}")
  fi
  # write to stderr
  echo "$line" >&2
  # if stderr not a TTY, also mirror to the terminal with colors
  if ! [ -t 2 ] && [ -w /dev/tty ]; then
    echo "$line" > /dev/tty
  fi
}

log_debug() {
    if [[ "$DEBUG_OUTPUT" == true ]]; then
        printf "${BLUE}[DEBUG]${NC} %s\n" "$1"
    fi
}

log_error() {
  local line
  if _supports_color; then
    line=$(printf '%b[ERROR]%b %s\n' "${RED-}" "${NC-}" "${1-}")
  else
    line=$(printf '[ERROR] %s\n' "${1-}")
  fi
  echo "$line" >&2
  if ! [ -t 2 ] && [ -w /dev/tty ]; then
    echo "$line" > /dev/tty
  fi
}

show_logo() {
    echo "      ___           ___           ___           ___           ___           ___           ___     "
    echo "     /\  \         /\  \         /\  \         /\  \         /\__\         /\  \         /\__\    "
    echo "    /::\  \       /::\  \       /::\  \       /::\  \       /::|  |       /::\  \       /::|  |   "
    echo "   /:/\:\  \     /:/\:\  \     /:/\:\  \     /:/\:\  \     /:|:|  |      /:/\:\  \     /:|:|  |   "
    echo "  /:/  \:\  \   /:/  \:\  \   /:/  \:\  \   /:/  \:\__\   /:/|:|__|__   /::\~\:\  \   /:/|:|__|__ "
    echo " /:/__/_\:\__\ /:/__/ \:\__\ /:/__/ \:\__\ /:/__/ \:|__| /:/ |::::\__\ /:/\:\ \:\__\ /:/ |::::\__\\"
    echo " \:\  /\ \/__/ \:\  \ /:/  / \:\  \ /:/  / \:\  \ /:/  / \/__/~~/:/  / \:\~\:\ \/__/ \/__/~~/:/  /"
    echo "  \:\ \:\__\    \:\  /:/  /   \:\  /:/  /   \:\  /:/  /        /:/  /   \:\ \:\__\         /:/  / "
    echo "   \:\/:/  /     \:\/:/  /     \:\/:/  /     \:\/:/  /        /:/  /     \:\ \/__/        /:/  /  "
    echo "    \::/  /       \::/  /       \::/  /       \::/__/        /:/  /       \:\__\         /:/  /   "
    echo "     \/__/         \/__/         \/__/         ~~            \/__/         \/__/         \/__/    "
    echo ""
    echo "                                   Memory APIs with CLI and UI"
    echo ""
}

# Check if command exists
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# Check if we can prompt the user interactively
can_prompt() {
    # Check if /dev/tty exists and is accessible for reading/writing
    # This works even when script is piped through curl
    [[ -c /dev/tty && -r /dev/tty && -w /dev/tty ]] && \
    # Additional check: try to actually read from /dev/tty
    exec 3< /dev/tty 2>/dev/null && exec 3<&- 2>/dev/null
}

# --- COS detection & compose wrapper ---

# Return 0 if we're on GCP Container-Optimized OS
is_cos() {
    # Most reliable: ID=cos in /etc/os-release
    if [[ -r /etc/os-release ]] && grep -q '^ID=cos' /etc/os-release; then
        return 0
    fi
    # Fallback heuristics
    [[ -d /usr/share/google/cos ]] && return 0
    return 1
}

# Set COMPOSE_BIN to a containerized docker-compose (v1) if v2 is not available or not runnable.
# Also defines an optional dcomp() shell function for interactive humans.
setup_compose_wrapper_if_needed() {
    # If docker compose v2 works, use it.
    if command_exists docker && docker compose version >/dev/null 2>&1; then
        COMPOSE_BIN=(docker compose)
        return 0
    fi

    # If legacy docker-compose (host binary) exists and works, use it.
    if command_exists docker-compose && docker-compose version >/dev/null 2>&1; then
        COMPOSE_BIN=(docker-compose)
        return 0
    fi

    # Otherwise, fall back to containerized compose v1 (works on COS/noexec).
    # Pin Docker API version to avoid compatibility issues with newer Docker Engine.
    local image="${COMPOSE_IMAGE:-docker/compose:1.29.2}"
    local api="${COMPOSE_API_VERSION:-1.41}"
    COMPOSE_BIN=(docker run --rm
        -e DOCKER_API_VERSION="$api"
        -e COMPOSE_API_VERSION="$api"
        -v /var/run/docker.sock:/var/run/docker.sock
        -v "$PWD:$PWD" -w "$PWD"
        "$image"
    )

    # If we're on an interactive shell, drop a helper function for the user.
    # We can't write system-wide on COS, so ~/.bashrc is the right place.
    if [[ -t 1 ]]; then
        if ! grep -q 'function dcomp' "$HOME/.bashrc" 2>/dev/null; then
            cat <<'EOF' >> "$HOME/.bashrc"

# Docker Compose wrapper (containerized) - added by GoodMem installer
function dcomp() {
  docker run --rm \
    -e DOCKER_API_VERSION="${COMPOSE_API_VERSION:-1.41}" \
    -e COMPOSE_API_VERSION="${COMPOSE_API_VERSION:-1.41}" \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v "$PWD:$PWD" -w "$PWD" \
    ${COMPOSE_IMAGE:-docker/compose:1.29.2} "$@"
}
EOF
        fi
    fi

    return 0
}

# Validate compose files for malformed environment variable references
validate_compose_env_refs() {
    local file="$1"
    local bad
    # Skip if file doesn't exist
    [[ ! -f "$file" ]] && return 0
    # catch accidental extra brace: ${DB_URL}}
    bad=$(grep -nE '\$\{(DB_URL|DB_USER|DB_PASSWORD)\}\}' "$file" || true)
    if [[ -n "$bad" ]]; then
        log_error "Invalid env interpolation (extra '}') in $file:"
        echo "$bad"
        exit 1
    fi
}

# Validate .env file for malformed database variables
validate_env_file() {
    local env="$1"
    # must not end with '}' and must be non-empty if set
    if [[ -f "$env" ]]; then
        if grep -qE '^(DB_URL|DB_USER|DB_PASSWORD)=.+\}' "$env"; then
            log_error "$env contains a trailing '}' in DB_* variables."
            exit 1
        fi
    fi
}

# Validate DSN userinfo (fail fast on unencoded reserved chars)
validate_dsn_userinfo() {
    local dsn="$1" rest userinfo host_and_path authority
    case "$dsn" in
        postgresql://* ) rest="${dsn#postgresql://}" ;;
        postgres://*   ) rest="${dsn#postgres://}"   ;;
        * ) log_error "--db-url must start with postgresql:// or postgres://"; return 1 ;;
    esac
    # If there's no '@', no userinfo → nothing to validate here
    [[ "$rest" != *@* ]] && return 0
    userinfo="${rest%%@*}"
    host_and_path="${rest#*@}"
    # authority = host[:port] up to first '/' or '?'
    authority="${host_and_path%%/*}"
    authority="${authority%%\?*}"

    # Unencoded reserved characters that must NOT appear in userinfo
    if [[ "$userinfo" == *'@'* || "$userinfo" == *'/'* || "$userinfo" == *'?'* || "$userinfo" == *'#'* ]]; then
        log_error "Invalid --db-url: credentials contain unencoded reserved characters (@, /, ?, #)."
        log_error "Percent-encode them (e.g., '@' → %40)."
        return 1
    fi
    # Basic authority sanity (common symptom of an unencoded '@' in password)
    if [[ -z "$authority" || "$authority" == *@* ]]; then
        log_error "Invalid --db-url: host part looks malformed (likely due to an unencoded '@' in the password)."
        log_error "Please percent-encode reserved characters in credentials."
        return 1
    fi
    return 0
}

# Process and validate a database DSN, setting required global variables
#
# Args:
#   $1 - PostgreSQL DSN (postgresql://user:pass@host:port/db?params)
#
# Side effects (sets global variables):
#   - DB_URL_DSN: The validated DSN in libpq format
#   - DB_URL_JDBC: The DSN converted to JDBC format for Java server
#   - DB_USER: Username extracted from DSN (only if not already set)
#   - DB_PASSWORD: Password extracted from DSN (only if not already set)
#
# Returns:
#   0 on success, 1 on validation failure
process_database_dsn() {
    local dsn="$1"

    if [[ -z "$dsn" ]]; then
        log_error "DSN cannot be empty"
        return 1
    fi

    # Validate DSN format and fail fast on unencoded credentials
    if ! validate_dsn_userinfo "$dsn"; then
        return 1
    fi

    # Set the main DSN variable
    DB_URL_DSN="$dsn"

    # Opportunistically extract DB_USER/DB_PASSWORD if not already set
    [[ -z "$DB_USER" ]] && DB_USER="$(printf '%s' "$dsn" | sed -nE 's#^postgres(ql)?://([^:@/]+).*$#\2#p')"
    if [[ -z "$DB_PASSWORD" && "$dsn" =~ :[^@/]+@ ]]; then
        DB_PASSWORD="$(printf '%s' "$dsn" | sed -nE 's#^postgres(ql)?://[^:]+:([^@/]+)@.*$#\2#p')"
    fi

    # Convert to JDBC format for server
    DB_URL_JDBC="$(dsn_to_jdbc "$dsn")"
    if [[ -z "$DB_URL_JDBC" ]]; then
        log_error "Invalid DSN for database URL conversion"
        return 1
    fi

    return 0
}

# Converts libpq DSN (postgres:// or postgresql://) -> JDBC URL.
dsn_to_jdbc() {
    local dsn="$1" rest userinfo host_and_path user pass sep jdbc
    case "$dsn" in
        postgresql://* ) rest="${dsn#postgresql://}" ;;
        postgres://*   ) rest="${dsn#postgres://}"   ;;
        * ) printf '' ; return 1 ;;
    esac

    if [[ "$rest" == *@* ]]; then
        userinfo="${rest%%@*}"     # user[:password]
        host_and_path="${rest#*@}" # host[:port]/db?qs
        user="${userinfo%%:*}"
        [[ "$userinfo" == *:* ]] && pass="${userinfo#*:}"
    else
        host_and_path="$rest"
    fi

    jdbc="jdbc:postgresql://$host_and_path"
    sep='?'; [[ "$jdbc" == *\?* ]] && sep='&'

    # Only append if not already present as query params
    if [[ "$jdbc" != *'?user='* && "$jdbc" != *'&user='* && -n "$user" ]]; then
        jdbc="${jdbc}${sep}user=${user}"; sep='&'
    fi
    if [[ "$jdbc" != *'?password='* && "$jdbc" != *'&password='* && -n "$pass" ]]; then
        jdbc="${jdbc}${sep}password=${pass}"
    fi
    printf '%s\n' "$jdbc"
}

# Build DSN from individual parts (for preflight)
build_libpq_dsn_from_parts() {
    [[ -z "$DB_HOST" || -z "$DB_NAME" ]] && { printf ''; return 1; }
    local auth=""
    [[ -n "$DB_USER" ]] && auth="$DB_USER"
    [[ -n "$DB_PASSWORD" ]] && auth="$auth:${DB_PASSWORD}"
    [[ -n "$auth" ]] && auth="${auth}@"
    local dsn="postgresql://${auth}${DB_HOST}:${DB_PORT}/${DB_NAME}"
    if [[ -n "$DB_SSL_MODE" ]]; then
        dsn="${dsn}?sslmode=${DB_SSL_MODE}"
    fi
    printf '%s\n' "$dsn"
}

# Build JDBC from individual parts (for server)
build_jdbc_from_parts() {
    [[ -z "$DB_HOST" || -z "$DB_NAME" ]] && { printf ''; return 1; }
    local jdbc="jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}"
    local sep='?'
    [[ -n "$DB_SSL_MODE" ]] && { jdbc="${jdbc}${sep}sslmode=${DB_SSL_MODE}"; sep='&'; }
    [[ -n "$DB_USER"     ]] && { jdbc="${jdbc}${sep}user=${DB_USER}"; sep='&'; }
    [[ -n "$DB_PASSWORD" ]] && { jdbc="${jdbc}${sep}password=${DB_PASSWORD}"; }
    printf '%s\n' "$jdbc"
}


# Test TCP connectivity to a host:port
tcp_can_connect() {
    local host="$1" port="$2"

    # Test override (used by unit/integration tests)
    if [[ "${GOODMEM_TEST:-0}" == "1" ]]; then
        case "${GOODMEM_TCP_MOCK:-}" in
            success) return 0 ;;
            fail)    return 1 ;;
            *)       echo "Invalid GOODMEM_TCP_MOCK (use 'success'|'fail')" >&2; return 2 ;;
        esac
    fi

    # Prefer nc with timeout; fall back to /dev/tcp for minimal deps
    if command -v nc >/dev/null 2>&1; then
        # BusyBox vs GNU nc both support -z; -w is widely supported
        nc -z -w2 "$host" "$port"
    else
        # Use timeout if present to avoid hangs
        if command -v timeout >/dev/null 2>&1; then
            timeout 3 bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null
        else
            (exec 3<>"/dev/tcp/$host/$port") 2>/dev/null
        fi
        local rc=$?
        # Close FD 3 if opened (harmless if not)
        exec 3>&- 3<&- || true
        return $rc
    fi
}

# Run preflight checks on remote database
preflight_database_checks() {
    log_info "Running preflight database checks..."

    # Prefer GCP mirror on COS to avoid orgs blocking Docker Hub pulls
    local PSQL_IMAGE="${PSQL_IMAGE:-mirror.gcr.io/library/postgres:17}"
    local PSQL_IMAGE_FALLBACK="postgres:17"
    
    # Get the libpq DSN for preflight checks (psql)
    local connection_string
    if [[ -n "$DB_URL_DSN" ]]; then
        connection_string="$DB_URL_DSN"
    else
        connection_string="$(build_libpq_dsn_from_parts)"
        if [[ -z "$connection_string" ]]; then
            log_error "Cannot build connection string from provided components"
            return 1
        fi
    fi
    
    # Set password via env if not in DSN (avoid prompt)
    local env_args=()
    env_args+=(-e PGCONNECT_TIMEOUT="${PGCONNECT_TIMEOUT:-10}")
    if [[ -n "$DB_PASSWORD" && "$connection_string" != *"$DB_PASSWORD"* ]]; then
        env_args+=(-e PGPASSWORD="$DB_PASSWORD")
    fi

    # Parse host/port from connection string for TCP check if not explicitly set
    if [[ -z "$DB_HOST" ]]; then
        DB_HOST="$(printf '%s' "$connection_string" | sed -nE 's#^[a-z]+://([^/@:\[\]]+)(:|/).*#\1#p')"
    fi
    if [[ -z "$DB_PORT" ]]; then
        DB_PORT="$(printf '%s' "$connection_string" | sed -nE 's#^[a-z]+://[^/@:\[\]]+:([0-9]+).*#\1#p')"
        DB_PORT="${DB_PORT:-5432}"
    fi

    # Quick host-level TCP check (catches VPC/firewall/route issues fast)
    if [[ -n "$DB_HOST" && -n "$DB_PORT" ]]; then
        log_debug "TCP reachability check: $DB_HOST:$DB_PORT"
        if ! tcp_can_connect "$DB_HOST" "$DB_PORT"; then
            # Optional: show resolved IP(s) to make network debugging easier
            if command -v getent >/dev/null 2>&1; then
                log_debug "Resolved $DB_HOST to: $(getent ahosts "$DB_HOST" | awk '{print $1}' | sort -u | paste -sd, -)"
            fi
            log_error "❌ Host cannot open TCP to $DB_HOST:$DB_PORT. Check firewall/VPC routes."
            return 1
        fi
        log_debug "✓ TCP connection to $DB_HOST:$DB_PORT successful"
    fi

    # Make sure we can pull a psql client image
    if ! docker image inspect "$PSQL_IMAGE" >/dev/null 2>&1; then
        log_info "Pulling psql client image ($PSQL_IMAGE)..."
        if ! docker pull -q "$PSQL_IMAGE" >/dev/null 2>&1; then
            log_warning "Mirror pull failed; falling back to Docker Hub ($PSQL_IMAGE_FALLBACK)..."
            if ! docker pull -q "$PSQL_IMAGE_FALLBACK" >/dev/null 2>&1; then
                log_error "❌ Could not pull a psql client image (mirror and hub). On COS, egress to Docker Hub is often blocked."
                log_error "   Set PSQL_IMAGE to a registry you can reach (eg, Artifact Registry mirror) and retry."
                return 1
            fi
            PSQL_IMAGE="$PSQL_IMAGE_FALLBACK"
        fi
    fi
    log_debug "✓ Using psql image: $PSQL_IMAGE"
    
    log_info "Testing database connectivity..."
    # Disable -e/pipefail locally so we can capture and print the real error
    local _old_errexit _old_pipefail
    _old_errexit=$(set -o | awk '/errexit/ {print $2}')
    _old_pipefail=$(set -o | awk '/pipefail/ {print $2}')
    set +e +o pipefail
    local _out _rc
    _out="$(docker run --rm "${env_args[@]}" "$PSQL_IMAGE" \
              psql "$connection_string" -v ON_ERROR_STOP=1 -Atc "SELECT 1" 2>&1)"
    _rc=$?
    # restore shell options
    [[ $_old_errexit == on ]] && set -e || set +e
    [[ $_old_pipefail == on ]] && set -o pipefail || set +o pipefail
    if (( _rc != 0 )) || [[ "$_out" != "1" ]]; then
        log_error "❌ psql failed (exit=$_rc)."
        # redact password if it sneaks into output
        [[ -n "$DB_PASSWORD" ]] && _out="${_out//"$DB_PASSWORD"/"***"}"
        log_error "$_out"
        log_error "Connection (sanitized): $(echo "$connection_string" | sed 's#://[^:@/]*:\?[^@/]*@#://***:***@#')"
        return 1
    fi
    log_success "✓ Database connectivity verified"
    
    log_info "Checking PostgreSQL version..."
    local pg_version
    if pg_version=$(docker run --rm "${env_args[@]}" "$PSQL_IMAGE" \
        psql "$connection_string" -t -c "SELECT version()" 2>/dev/null | head -n1); then
        log_success "✓ PostgreSQL version: $(echo "$pg_version" | xargs)"
        # Extract major version for comparison (e.g., "PostgreSQL 15.3" -> "15")
        local major_version
        major_version=$(echo "$pg_version" | grep -oE '[0-9]+\.[0-9]+' | head -n1 | cut -d. -f1)
        if [[ -n "$major_version" && "$major_version" -lt 15 ]]; then
            log_warning "⚠️  PostgreSQL version $major_version detected. Minimum recommended is 15."
        fi
    else
        log_warning "⚠️  Could not determine PostgreSQL version"
    fi
    
    log_info "Checking for pgvector extension..."
    if docker run --rm "${env_args[@]}" "$PSQL_IMAGE" \
        psql "$connection_string" -t -c "SELECT extname FROM pg_extension WHERE extname='vector'" 2>/dev/null | grep -q vector; then
        log_success "✓ pgvector extension is available"
    else
        log_warning "⚠️  pgvector extension not found"
        log_warning "   The server will attempt to create it, but this may require superuser privileges"
        log_warning "   If installation fails, contact your DBA to install the pgvector extension"
    fi
    
    log_info "Checking schema creation permissions..."
    if docker run --rm "${env_args[@]}" "$PSQL_IMAGE" \
        psql "$connection_string" -c "CREATE SCHEMA IF NOT EXISTS goodmem_test; DROP SCHEMA IF EXISTS goodmem_test;" >/dev/null 2>&1; then
        log_success "✓ Schema creation permissions verified"
    else
        log_error "❌ Cannot create schemas"
        log_error "   The user needs permissions to create schemas 'goodmem' and 'jobrunr'"
        log_error "   Contact your DBA to grant schema creation privileges"
        return 1
    fi
    
    log_success "All preflight checks passed!"
    return 0
}

# Download file helper with fallback to wget
dl() {
    local url="$1" output="$2"
    local tmp
    tmp=$(mktemp) || return 1
    temp_files+=("$tmp")
    if command_exists curl; then
        curl -fsSL "$url" -o "$tmp" || return 1
    elif command_exists wget; then
        wget -qO "$tmp" "$url" || return 1
    else
        log_error "Neither curl nor wget found. Please install one of them."
        return 1
    fi
    cp -f "$tmp" "$output" && rm -f "$tmp"
}

# Extract archive helper
extract_archive() {
    local archive_file="$1"
    local dest_dir="$2"
    local platform="$3"
    
    case "$platform" in
        windows-*)
            if ! command_exists unzip; then
                log_error "Required tool 'unzip' not found. Install it and retry."
                return 1
            fi
            unzip -j "$archive_file" -d "$dest_dir" >/dev/null 2>&1
            ;;
        *)
            if ! command_exists tar; then
                log_error "Required tool 'tar' not found. Install it and retry."
                return 1
            fi
            tar -xzf "$archive_file" -C "$dest_dir" >/dev/null 2>&1
            ;;
    esac
}

# Detect Docker Compose command and set global COMPOSE_BIN array
detect_compose() {
    # Normal platforms: use v2 if available, else v1.
    if ! is_cos; then
        if command_exists docker && docker compose version >/dev/null 2>&1; then
            COMPOSE_BIN=(docker compose)
            return 0
        fi
        if command_exists docker-compose && docker-compose version >/dev/null 2>&1; then
            COMPOSE_BIN=(docker-compose)
            return 0
        fi
        # Check for plugin files even if version check fails
        if [[ -x /usr/lib/docker/cli-plugins/docker-compose || -x /usr/libexec/docker/cli-plugins/docker-compose ]]; then
            COMPOSE_BIN=(docker compose)
            return 0
        fi
        log_error "Docker Compose not found. Try: apt-get install -y docker-compose-plugin"
        return 1
    fi

    # COS path: try v2/v1, else use containerized v1 wrapper.
    log_info "GCP Container-Optimized OS detected"
    setup_compose_wrapper_if_needed || {
        log_error "Failed to configure Docker Compose wrapper on COS."
        return 1
    }
    return 0
}

# Check if Docker Compose version is greater than or equal to specified version
compose_version_ge() {
    local have want
    have="$("${COMPOSE_BIN[@]}" version 2>/dev/null | grep -oE '([0-9]+\.)+[0-9]+' | head -n1)"
    want="$1"
    [[ -n "$have" ]] && printf '%s\n%s\n' "$want" "$have" | sort -V | tail -n1 | grep -q "^$have"
}

# Require Docker Compose >= 2.20.2 for remote database (depends_on.required=false)
require_compose_for_remote() {
    if [[ "$USE_REMOTE_DB" == true ]]; then
        if is_cos; then
            # On COS we run containerized v1. We use base+overlay strategy instead of v2 features.
            log_info "COS detected: using containerized Compose v1 with base+overlay strategy."
            return 0
        fi
        if ! compose_version_ge "2.20.2"; then
            log_error "Docker Compose v2.20.2+ required for remote DB (uses depends_on.required=false)."
            log_error "Please upgrade docker compose (e.g., install docker-compose-plugin) and re-run."
            exit 1
        fi
    fi
}

# Validate remote database configuration flags
validate_remote_db_config() {
    if [[ "$USE_REMOTE_DB" == true ]]; then
        if [[ -z "$DB_URL_DSN" ]]; then
            local missing=()
            [[ -z "$DB_HOST" || -z "${DB_HOST// }" ]]     && missing+=("--db-host")
            [[ -z "$DB_NAME" || -z "${DB_NAME// }" ]]     && missing+=("--db-name")
            [[ -z "$DB_USER" || -z "${DB_USER// }" ]]     && missing+=("--db-user")
            [[ -z "$DB_PASSWORD" || -z "${DB_PASSWORD// }" ]] && missing+=("--db-password")
            if (( ${#missing[@]} )); then
                if [[ -z "${GOODMEM_TEST_SOURCING:-}" ]]; then
                    log_error "Remote DB requires either --db-url (DSN) OR all of: ${missing[*]}"
                    exit 1
                else
                    echo "Remote DB requires either --db-url (DSN) OR all of: ${missing[*]}"
                    return 1
                fi
            fi
            # Build both forms since we have parts only
            if ! DB_URL_DSN="$(build_libpq_dsn_from_parts)"; then
                if [[ -z "${GOODMEM_TEST_SOURCING:-}" ]]; then
                    log_error "Invalid DB parts"
                    exit 1
                else
                    echo "Invalid DB parts"
                    return 1
                fi
            fi
            if ! DB_URL_JDBC="$(build_jdbc_from_parts)"; then
                if [[ -z "${GOODMEM_TEST_SOURCING:-}" ]]; then
                    log_error "Invalid DB parts"
                    exit 1
                else
                    echo "Invalid DB parts"
                    return 1
                fi
            fi
        fi
    fi
}

# Build compose file arguments based on installation mode and platform
compose_files() {
    local goodmem_dir="$1"
    local platform="$2"
    
    log_debug "compose_files() function called with goodmem_dir=$goodmem_dir, platform=$platform"
    
    # Force variable evaluation and output buffer flush (fixes race condition)
    printf "" >/dev/null
    
    COMPOSE_FILES_ARGS=(-f "$goodmem_dir/docker-compose.yml")
    
    # Debug: Show remote database status
    log_debug "Database mode: USE_REMOTE_DB=$USE_REMOTE_DB"
    log_debug "About to check USE_REMOTE_DB condition..."
    log_debug "Condition test result: [[ \"$USE_REMOTE_DB\" != true ]] = $([[ "$USE_REMOTE_DB" != true ]] && echo "TRUE" || echo "FALSE")"
    
    # Add local database files if not using remote
    if [[ "$USE_REMOTE_DB" != true ]]; then
        COMPOSE_FILES_ARGS+=(--profile local-db)
        COMPOSE_FILES_ARGS+=(-f "$goodmem_dir/docker-compose.local-db.yml")
        log_debug "Added local-db profile and dependency overlay (local database mode)"
        log_debug "COMPOSE_FILES_ARGS after adding local-db: ${COMPOSE_FILES_ARGS[*]}"
    else
        log_info "Skipped local-db profile (remote database mode)"
        log_debug "COMPOSE_FILES_ARGS without local-db: ${COMPOSE_FILES_ARGS[*]}"
    fi
    
    # Force array assignment to complete before continuing
    printf "" >/dev/null
    
    [[ "$INSTALL_MODE" == "debug" ]] && COMPOSE_FILES_ARGS+=(-f "$goodmem_dir/docker-compose.debug.yml") && log_debug "Added debug overlay file"
    [[ "$platform" == "darwin-arm64" ]] && COMPOSE_FILES_ARGS+=(-f "$goodmem_dir/docker-compose.macos-arm64.yml") && log_debug "Added macOS ARM64 overlay file"
    
    log_debug "Final COMPOSE_FILES_ARGS: ${COMPOSE_FILES_ARGS[*]}"
    
    # Ensure function completes properly
    printf "" >/dev/null
}

#Check if a local port is in use
# The function takes two arguments:
# 1. Port number to check
# 2. Warning message to display if the port is in use
# It quits the installer if the port is in use.
check_port_availability() {
    local port="$1"
    local warning_message="$2"
    
    # Method 1: Try /proc/net/tcp (Linux, WSL2, COS - no sudo required)
    if [[ -f /proc/net/tcp ]]; then
        # Convert port to hexadecimal
        local hex_port=$(printf "%04X" "$port")
        
        if grep -q ":${hex_port} " /proc/net/tcp 2>/dev/null || \
           ([ -f /proc/net/tcp6 ] && grep -q ":${hex_port} " /proc/net/tcp6 2>/dev/null); then
            log_warning "$warning_message"
            log_warning "Exiting due to port $port being in use."
            exit 1
        fi
        return 0
    fi
    
    # Method 2: Try ss (socket statistics - modern Linux standard)
    if command -v ss >/dev/null 2>&1; then
        # Check if port is in listening state
        if ss -ln | grep -q ":${port} " 2>/dev/null; then
            log_warning "$warning_message"
            log_warning "Exiting due to port $port being in use."
            exit 1
        fi
        return 0
    fi
    
    # Method 3: Try lsof (macOS and Linux when available)
    if command -v lsof >/dev/null 2>&1; then
        # Try without sudo first (might work for ports owned by current user)
        if lsof -i ":${port}" >/dev/null 2>&1; then
            log_warning "$warning_message"
            log_warning "Exiting due to port $port being in use."
            exit 1
        fi
        
        # Always try with sudo if available, as lsof without sudo may not see
        # ports occupied by other users or system processes
        if command -v sudo >/dev/null 2>&1; then
            log_debug "Checking port with elevated permissions for complete visibility..."
            if sudo lsof -i ":${port}" >/dev/null 2>&1; then
                log_warning "$warning_message"
                log_warning "Exiting due to port $port being in use."
                exit 1
            fi
        else
            log_debug "Cannot perform complete port check without sudo access"
        fi
        # Don't return here - continue with other methods as fallback
    fi
    
    # Method 4: Try nc (netcat) as a fallback
    if command -v nc >/dev/null 2>&1; then
        # Try to connect to the port
        if nc -z localhost "$port" 2>/dev/null; then
            log_warning "$warning_message"
            log_warning "Exiting due to port $port being in use."
            exit 1
        fi
        return 0
    fi
    
    # Method 5: Try bash's /dev/tcp if available
    if [[ -n "$BASH_VERSION" ]]; then
        if (exec 3<>/dev/tcp/localhost/"$port") 2>/dev/null; then
            exec 3>&-  # Close the file descriptor
            log_warning "$warning_message"
            log_warning "Exiting due to port $port being in use."
            exit 1
        fi
        return 0
    fi

    # If the port failed all checks (which is good), we still print a warning that it may be due to our inabilility to check
    log_warning "Port $port does not seem to be in use. But it may be occupied but cannot be detected. Continuing for now but you may run into issues due to port availability."
    return 0
}

# Prompt for installation mode if not specified
prompt_installation_mode() {
    if [[ -n "$INSTALL_MODE" ]]; then
        log_info "Installation mode: $INSTALL_MODE (specified via command line)"
        return 0
    fi
    
    echo ""
    echo "🔧 Installation Mode Selection"
    echo ""
    echo "Choose your installation mode:"
    if [[ "$USE_REMOTE_DB" == true ]]; then
        echo "  1) Production mode (minimal port exposure, recommended for servers)"
        echo "  2) Debug mode (enables additional debugging features like JobRunr dashboard)"
    else
        echo "  1) Production mode (database port not exposed, recommended for servers)"
        echo "  2) Debug mode (database port exposed on 5432, useful for development)"
    fi
    echo ""
    
    while true; do
        printf "Select installation mode [1=Production, 2=Debug] (default: 2): "
        read -r REPLY </dev/tty
        # Default to debug mode if empty
        REPLY="${REPLY:-2}"
        case $REPLY in
            1)
                INSTALL_MODE="prod"
                log_info "Selected: Production mode"
                break
                ;;
            2)
                INSTALL_MODE="debug"
                if [[ "$USE_REMOTE_DB" == true ]]; then
                    log_info "Selected: Debug mode (enables additional debugging features like JobRunr dashboard)"
                else
                    log_info "Selected: Debug mode (database accessible on localhost:5432)"
                    # Check if local port DB_PORT is already in use
                    warning_message="Port $DB_PORT for PG is already in use while you selected debug mode. Please free the port or choose production mode."
                    check_port_availability "$DB_PORT" "$warning_message"

                    # Check whether jobrunr port is in use
                    warning_message="Port 8001 for JobRunr is already in use while you selected debug mode. Please free the port or choose production mode."
                    check_port_availability "8001" "$warning_message"
                fi
                break
                ;;
            *)
                echo "Please enter 1 or 2"
                ;;
        esac
    done
    echo ""
}

# Prompt for database location and configuration if not specified
prompt_database_configuration() {
    # Skip if local database is specified via command line
    if [[ "$USE_LOCAL_DB" == true ]]; then
        log_info "Database configuration: Local database (specified via command line)"
        USE_REMOTE_DB=false
        return 0
    fi

    # Skip if remote database is already configured via command line
    if [[ "$USE_REMOTE_DB" == true ]]; then
        log_info "Database configuration: Remote database (specified via command line)"
        return 0
    fi

    # If no TTY available, default to local database
    if [[ ! -t 0 ]]; then
        log_info "No TTY detected, defaulting to local database"
        USE_REMOTE_DB=false
        return 0
    fi

    echo ""
    echo "🗄️  Database Configuration"
    echo ""
    echo "Choose your database setup:"
    echo "  1) Local PostgreSQL (Docker container, easier setup)"
    echo "  2) Remote PostgreSQL (connect to existing database)"
    echo ""
    
    while true; do
        printf "Select database setup [1=Local, 2=Remote] (default: 1): "
        read -r REPLY </dev/tty
        # Default to local database if empty
        REPLY="${REPLY:-1}"
        case $REPLY in
            1)
                USE_REMOTE_DB=false
                log_info "Selected: Local PostgreSQL database"
                break
                ;;
            2)
                USE_REMOTE_DB=true
                log_info "Selected: Remote PostgreSQL database"
                prompt_remote_database_details
                break
                ;;
            *)
                echo "Please enter 1 or 2"
                ;;
        esac
    done
    echo ""
}

# Prompt for remote database connection details
prompt_remote_database_details() {
    echo ""
    echo "📡 Remote Database Configuration"
    echo ""
    echo "Please provide your PostgreSQL database connection details:"
    echo ""
    
    # Ask if they want to provide a full DSN or individual components
    echo "You can provide either:"
    echo "  1) Full PostgreSQL URL (e.g., postgresql://user:pass@host:5432/dbname)"
    echo "  2) Individual connection details"
    echo ""
    
    while true; do
        printf "Select input method (1 for URL, 2 for individual details): "
        read -r REPLY </dev/tty
        case $REPLY in
            1)
                echo ""
                echo "Please enter your PostgreSQL URL:"
                echo "Format: postgresql://username:password@hostname:port/database?sslmode=require"
                echo ""
                printf "PostgreSQL URL: "
                read -r user_dsn </dev/tty
                echo ""

                if [[ -z "$user_dsn" ]]; then
                    log_error "URL cannot be empty. Please try again."
                    continue
                fi

                if [[ ! "$user_dsn" =~ ^postgresql:// ]]; then
                    log_warning "URL should start with 'postgresql://' but continuing anyway..."
                fi

                if ! process_database_dsn "$user_dsn"; then
                    continue
                fi

                log_info "Using provided PostgreSQL URL"
                break
                ;;
            2)
                echo ""
                printf "Database Host: "
                read -r DB_HOST </dev/tty
                printf "Database Port [5432]: "
                read -r db_port_input </dev/tty
                DB_PORT="${db_port_input:-5432}"
                printf "Database Name: "
                read -r DB_NAME </dev/tty
                printf "Database User: "
                read -r DB_USER </dev/tty
                printf "Database Password: "
                read -s -r DB_PASSWORD </dev/tty
                echo ""
                
                # SSL Mode selection
                echo ""
                echo "SSL Mode options:"
                echo "  1) verify-full (recommended, verifies hostname and certificate)"
                echo "  2) require (encrypts connection but doesn't verify hostname)"
                echo "  3) prefer (use SSL if available, fallback to non-SSL)"
                echo "  4) disable (no SSL encryption)"
                echo ""
                
                while true; do
                    printf "Select SSL mode (1-4) [2]: "
                    read -r ssl_choice </dev/tty
                    case "${ssl_choice:-2}" in
                        1) DB_SSL_MODE="verify-full"; break ;;
                        2) DB_SSL_MODE="require"; break ;;
                        3) DB_SSL_MODE="prefer"; break ;;
                        4) DB_SSL_MODE="disable"; break ;;
                        *) echo "Please enter 1, 2, 3, or 4" ;;
                    esac
                done
                
                # Validate required fields
                if [[ -z "$DB_HOST" || -z "$DB_NAME" || -z "$DB_USER" || -z "$DB_PASSWORD" ]]; then
                    log_error "All fields (host, database name, user, password) are required. Please try again."
                    continue
                fi
                
                # Build both DSN and JDBC formats now that we have all components
                DB_URL_DSN="$(build_libpq_dsn_from_parts)"
                DB_URL_JDBC="$(build_jdbc_from_parts)"
                
                echo ""
                log_info "Database configuration:"
                log_info "  Host: $DB_HOST"
                log_info "  Port: $DB_PORT"
                log_info "  Database: $DB_NAME"
                log_info "  User: $DB_USER"
                log_info "  SSL Mode: $DB_SSL_MODE"
                break
                ;;
            *)
                echo "Please enter 1 or 2"
                ;;
        esac
    done
}

# Detect OS and architecture (WSL aware)
detect_platform() {
    local os
    local arch

    # Detect architecture
    case "$(uname -m)" in
        x86_64|amd64)   arch="amd64" ;;
        arm64|aarch64)  arch="arm64" ;;
        armv7l)         arch="arm" ;;
        *)              arch="unknown" ;;
    esac

    # Detect WSL/WSL2
    is_wsl_env=false
    is_wsl2_env=false
    if [ -f /proc/sys/kernel/osrelease ] && grep -qiE "(microsoft|wsl)" /proc/sys/kernel/osrelease 2>/dev/null; then
        is_wsl_env=true
        if grep -qi "wsl2" /proc/sys/kernel/osrelease 2>/dev/null; then
            is_wsl2_env=true
        fi
    elif [ -f /proc/version ] && grep -qi "microsoft" /proc/version 2>/dev/null; then
        is_wsl_env=true
        if uname -r | grep -qi "WSL2"; then
            is_wsl2_env=true
        fi
    fi

    # Detect OS
    case "$(uname -s)" in
        Linux*)
            if $is_wsl_env; then
                if $is_wsl2_env; then
                    os="wsl2"
                else
                    os="wsl1"
                fi
            else
                os="linux"
            fi
            ;;
        Darwin*) os="darwin" ;;
        CYGWIN*|MINGW*|MSYS*) os="windows" ;;
        *) os="unknown" ;;
    esac

    echo "${os}-${arch}"
}

# Check Docker installation
check_docker() {
    log_info "Checking Docker installation..."

    if command_exists docker; then
        if docker info >/dev/null 2>&1; then
            log_success "Docker is installed and running"
            return 0
        fi

        # WSL: try socket link + actionable hint
        local platform
        platform=$(detect_platform)
        if [[ "$platform" == wsl*-* ]]; then
            if [ ! -S /var/run/docker.sock ] && [ -S /mnt/wsl/shared-docker/docker.sock ]; then
                log_info "Linking Docker Desktop socket into /var/run/docker.sock..."
                if command_exists sudo; then
                    sudo mkdir -p /var/run || true
                    sudo ln -sf /mnt/wsl/shared-docker/docker.sock /var/run/docker.sock || true
                fi
                if docker info >/dev/null 2>&1; then
                    log_success "Docker is installed and running via Docker Desktop"
                    return 0
                fi
            fi
            log_warning "Docker CLI found but engine is not reachable. Is Docker Desktop running and WSL integration enabled?"
            return 1
        else
            log_warning "Docker is installed but not running"
            return 1
        fi
    else
        log_warning "Docker is not installed"
        return 1
    fi
}

# --- WSL-specific helpers ----------------------------------------------------

# Add the current user to the docker group if the group exists
ensure_user_in_docker_group() {
    if getent group docker >/dev/null 2>&1; then
        if ! id -nG "$USER" 2>/dev/null | tr ' ' '\n' | grep -qx docker; then
            log_info "Adding $USER to 'docker' group..."
            if command_exists sudo; then
                sudo usermod -aG docker "$USER" || true
                log_warning "You may need to close and reopen your WSL terminal for group changes to take effect."
            fi
        fi
    fi
}

# Install only the Docker CLI inside Ubuntu on WSL. The engine is provided by Docker Desktop for Windows.
install_docker_cli_on_ubuntu_wsl() {
    if ! command_exists apt-get; then
        log_error "This WSL distribution does not appear to be Debian/Ubuntu based. Please install the docker CLI manually."
        return 1
    fi

    log_info "Installing Docker CLI (docker-ce-cli) and compose plugin inside WSL Ubuntu..."
    if ! command_exists curl; then
        log_info "Installing curl..."
        sudo apt-get update -y && sudo apt-get install -y curl
    fi

    # Install prerequisites
    sudo apt-get update -y
    sudo apt-get install -y ca-certificates curl gnupg lsb-release

    # Setup Docker's official GPG key (idempotent)
    sudo install -m 0755 -d /etc/apt/keyrings
    if [ ! -f /etc/apt/keyrings/docker.gpg ]; then
        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
        sudo chmod a+r /etc/apt/keyrings/docker.gpg
    fi

    # Setup repository (idempotent)
    local codename
    codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${codename} stable" | \
        sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

    sudo apt-get update -y
    sudo apt-get install -y docker-ce-cli docker-compose-plugin

    ensure_user_in_docker_group
}

# Give precise instructions for installing and enabling Docker Desktop on Windows with WSL integration
advise_docker_desktop_setup() {
    cat <<'EOT'

Docker Desktop for Windows is required on WSL 2:

  1) In Windows 11, install Docker Desktop: https://www.docker.com/products/docker-desktop/
  2) Open Docker Desktop → Settings → Resources → WSL Integration.
     - Enable "Enable integration with my default WSL distro"
     - Ensure your current distro is toggled ON.
  3) Start Docker Desktop. Inside WSL, verify the socket exists: /var/run/docker.sock
  4) If the socket doesn't exist but /mnt/wsl/shared-docker/docker.sock does, this installer will link it.

Then re-run this installer.

EOT
}

# Install Docker
install_docker() {
    local platform
    platform=$(detect_platform)
    
    log_info "Installing Docker for platform: $platform"
    
    case "$platform" in
        wsl*-*)
            log_info "Detected Windows Subsystem for Linux."
            if [[ "$platform" == "wsl1-"* ]]; then
                log_error "WSL 1 is not supported for Docker. Please upgrade this distro to WSL 2."
                advise_docker_desktop_setup
                return 1
            fi

            # Install the Docker CLI and compose plugin inside WSL
            if ! command_exists docker; then
                install_docker_cli_on_ubuntu_wsl || true
            fi

            # Ensure the Docker Desktop socket is reachable
            if [ -S /var/run/docker.sock ]; then
                log_info "Found Docker Desktop socket at /var/run/docker.sock"
            elif [ -S /mnt/wsl/shared-docker/docker.sock ]; then
                log_info "Found Docker Desktop socket at /mnt/wsl/shared-docker/docker.sock; creating link..."
                if command_exists sudo; then
                    sudo mkdir -p /var/run || true
                    sudo ln -sf /mnt/wsl/shared-docker/docker.sock /var/run/docker.sock || true
                else
                    ln -sf /mnt/wsl/shared-docker/docker.sock /var/run/docker.sock || true
                fi
            else
                log_warning "Docker Desktop for Windows does not appear to be running or WSL integration is disabled."
                advise_docker_desktop_setup
                return 1
            fi

            ensure_user_in_docker_group

            # Validate connectivity
            if docker info >/dev/null 2>&1; then
                log_success "Docker Desktop (engine) + docker-ce-cli (WSL) are ready."
                return 0
            else
                log_warning "Docker CLI is installed, but the engine is still unreachable."
                advise_docker_desktop_setup
                return 1
            fi
            ;;
        linux-*)
            log_info "Installing Docker using the official installation script..."
            if command_exists curl; then
                curl -fsSL https://get.docker.com | sh
            else
                local temp_install_script
                temp_install_script=$(mktemp)
                temp_files+=("$temp_install_script")
                dl "https://get.docker.com" "$temp_install_script"
                sh "$temp_install_script"
            fi
            
            # Add current user to docker group
            if ! id -nG "$USER" 2>/dev/null | tr ' ' '\n' | grep -qx docker; then
                log_info "Adding current user to docker group..."
                sudo usermod -aG docker "$USER"
                log_warning "You may need to log out and back in for Docker group changes to take effect"
            fi
            ;;
        darwin-*)
            # Check if Docker Desktop is already installed
            if command_exists docker || [ -d "/Applications/Docker.app" ]; then
                log_info "Docker Desktop is installed. Checking if it's running..."
                
                # If Docker is not running, try to start it
                if ! docker info >/dev/null 2>&1; then
                    log_info "Starting Docker Desktop..."
                    if command_exists open; then
                        # Start Docker Desktop - try multiple ways to find and start it
                        if [ -d "/Applications/Docker.app" ]; then
                            log_info "Starting Docker Desktop from /Applications/Docker.app..."
                            # Use nohup to start Docker Desktop in background without GUI focus
                            nohup open "/Applications/Docker.app" >/dev/null 2>&1 &
                            sleep 3
                        elif command_exists open; then
                            log_info "Starting Docker Desktop via application name..."
                            nohup open -Ra Docker >/dev/null 2>&1 &
                            sleep 3
                        else
                            log_error "Cannot find Docker Desktop application to start"
                            log_error "Please start Docker Desktop manually: open -a Docker"
                            exit 1
                        fi
                        
                        # Wait for Docker to start with proper timeout and retry logic
                        log_info "Waiting for Docker Desktop to start (this may take several minutes)..."
                        local max_wait=600  # 10 minutes
                        local wait_time=0
                        local check_interval=15
                        
                        while [ $wait_time -lt $max_wait ]; do
                            if docker info >/dev/null 2>&1; then
                                log_success "Docker Desktop started successfully"
                                return 0
                            fi
                            
                            log_info "Still waiting for Docker Desktop... (${wait_time}s/${max_wait}s)"
                            sleep $check_interval
                            wait_time=$((wait_time + check_interval))
                        done
                        
                        log_error "Docker Desktop failed to start within ${max_wait} seconds"
                        log_error "Please start Docker Desktop manually and re-run this installer"
                        exit 1
                    else
                        log_error "Cannot start Docker Desktop automatically (missing 'open' command)"
                        log_error "Please start Docker Desktop manually and re-run this installer"
                        exit 1
                    fi
                else
                    log_success "Docker Desktop is already running"
                    return 0
                fi
            else
                log_info "Installing Docker Desktop..."
                
                # Check if we're in a CI environment (GitHub Actions, etc.)
                if [[ -n "${CI:-}" || -n "${GITHUB_ACTIONS:-}" ]]; then
                    log_info "CI environment detected - using direct Docker Desktop installation"
                    
                    # Download Docker Desktop DMG
                    local docker_dmg="/tmp/Docker.dmg"
                    log_info "Downloading Docker Desktop..."
                    if command_exists curl; then
                        curl -fsSL "https://desktop.docker.com/mac/main/amd64/Docker.dmg" -o "$docker_dmg"
                    else
                        dl "https://desktop.docker.com/mac/main/amd64/Docker.dmg" "$docker_dmg"
                    fi
                    
                    # Install Docker Desktop using the official command-line method
                    log_info "Installing Docker Desktop with privileged configuration..."
                    sudo hdiutil attach "$docker_dmg"
                    
                    # Use --user flag to avoid GUI permission prompts in CI
                    local current_user="$(whoami)"
                    sudo "/Volumes/Docker/Docker.app/Contents/MacOS/install" --accept-license --user="$current_user"
                    
                    sudo hdiutil detach "/Volumes/Docker"
                    rm -f "$docker_dmg"
                    
                    log_success "Docker Desktop installed with CI-friendly configuration"
                    
                    # Start Docker Desktop
                    log_info "Starting Docker Desktop..."
                    # Docker should start automatically after installation with --user flag
                    
                else
                    # Interactive installation via Homebrew for local development
                    log_info "Installing Docker Desktop via Homebrew..."
                    if command_exists brew; then
                        brew install --cask docker-desktop
                        log_info "Docker Desktop installed. Starting it..."
                        
                        # Wait a moment for the app to be fully registered
                        sleep 3
                        
                        # Pre-configure Docker Desktop to avoid GUI prompts
                        log_info "Configuring Docker Desktop for headless operation..."
                        
                        # Create Docker Desktop settings directory if it doesn't exist
                        mkdir -p "$HOME/Library/Group Containers/group.com.docker"
                        
                        # Create a minimal settings file to skip setup wizard
                        cat > "$HOME/Library/Group Containers/group.com.docker/settings.json" << 'EOF'
{
  "analyticsEnabled": false,
  "autoStart": false,
  "checkForUpdates": false,
  "displayedOnboarding": true,
  "exposeDockerAPIOnTCP2375": false,
  "openUIOnStartupDisabled": true,
  "showSystemContainers": false,
  "skipUpdateToWSLPrompt": true,
  "terminalType": "integrated"
}
EOF
                        
                        # Start Docker Desktop - try multiple ways after installation
                        if [ -d "/Applications/Docker.app" ]; then
                            log_info "Starting Docker Desktop from /Applications/Docker.app..."
                            # Use nohup to start Docker Desktop in background without GUI focus
                            nohup open "/Applications/Docker.app" >/dev/null 2>&1 &
                            
                            # Give it a moment to start the background process
                            sleep 5
                        else
                            log_error "Docker Desktop was installed but cannot find /Applications/Docker.app"
                            log_error "Please start Docker Desktop manually and re-run this installer"
                            exit 1
                        fi
                        
                    else
                        log_error "Homebrew not found. Please install Docker Desktop manually:"
                        log_error "https://docker.com/products/docker-desktop"
                        exit 1
                    fi
                fi
                
                # Same waiting logic for both CI and interactive installations
                log_info "Waiting for Docker Desktop to start after installation..."
                local max_wait=600
                local wait_time=0
                local check_interval=15
                
                while [ $wait_time -lt $max_wait ]; do
                    if docker info >/dev/null 2>&1; then
                        log_success "Docker Desktop started successfully after installation"
                        return 0
                    fi
                    
                    log_info "Still waiting for Docker Desktop... (${wait_time}s/${max_wait}s)"
                    sleep $check_interval
                    wait_time=$((wait_time + check_interval))
                done
                
                log_error "Docker Desktop failed to start within ${max_wait} seconds after installation"
                log_error "Please start Docker Desktop manually and re-run this installer"
                exit 1
            fi
            ;;
        windows-*)
            log_error "Please install Docker Desktop for Windows from: https://docker.com/products/docker-desktop"
            log_error "Then run this installer again"
            exit 1
            ;;
        *)
            log_error "Unsupported platform: $platform"
            log_error "Please install Docker manually and run this installer again"
            exit 1
            ;;
    esac
}

# Setup environment variables for Docker Compose based on database configuration
setup_environment_variables() {
    local goodmem_dir="$1"
    
    if [[ "$USE_REMOTE_DB" == true ]]; then
        log_debug "Configuring environment for remote database..."
        
        # Get the JDBC URL for the server container
        local db_url_value
        if [[ -n "$DB_URL_JDBC" ]]; then
            db_url_value="$DB_URL_JDBC"
        else
            db_url_value="$(build_jdbc_from_parts)"
            if [[ -z "$db_url_value" ]]; then
                log_error "Cannot build JDBC URL from provided components"
                return 1
            fi
        fi
        
        cat > "$goodmem_dir/.env" <<EOF
# Remote database configuration
DB_URL=$db_url_value
DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD

# Data directory (not used for remote database, but kept for consistency)
DATA_DIR_BASE=$goodmem_dir/data
EOF
    else
        log_debug "Configuring environment for local database..."
        
        # Set defaults for local database
        local postgres_user="${POSTGRES_USER:-goodmem_user}"
        local postgres_password="${POSTGRES_PASSWORD:-goodmem_password}"
        local postgres_db="${POSTGRES_DB:-goodmem_db}"
        
        # For local database, construct JDBC URL and set DB variables for server
        cat > "$goodmem_dir/.env" <<EOF
# Local database configuration
POSTGRES_USER=$postgres_user
POSTGRES_PASSWORD=$postgres_password
POSTGRES_DB=$postgres_db

# Server database connection (constructed from POSTGRES_* values)
DB_URL=jdbc:postgresql://db:5432/$postgres_db
DB_USER=$postgres_user
DB_PASSWORD=$postgres_password

# Data directory for local database
DATA_DIR_BASE=$goodmem_dir/data
EOF
    fi
    
    log_debug "Environment configuration saved to $goodmem_dir/.env"
}

# Download GoodMem configuration files
download_goodmem_files() {
    local goodmem_dir="${GOODMEM_DIR:-$HOME/.goodmem}"
    local base_url="$BASE_URL"
    
    log_info "Setting up GoodMem directory: $goodmem_dir"
    
    # Create directory structure
    mkdir -p "$goodmem_dir"
    
    # Only create pgdata directory if we're using local database
    if [[ "$USE_REMOTE_DB" != true ]]; then
        mkdir -p "$goodmem_dir/data/pgdata"
    fi
    
    # Download base docker-compose.yml
    log_debug "Downloading base docker-compose.yml..."
    dl "$base_url/docker-compose.yml" "$goodmem_dir/docker-compose.yml"
    
    # Download local database overlay (always needed for potential local mode)
    log_debug "Downloading local database overlay..."
    dl "$base_url/docker-compose.local-db.yml" "$goodmem_dir/docker-compose.local-db.yml"
    
    # Download overlay files based on installation mode and platform
    local platform
    platform=$(detect_platform)
    
    if [[ "$INSTALL_MODE" == "debug" ]]; then
        log_debug "Downloading debug overlay for debug mode..."
        dl "$base_url/docker-compose.debug.yml" "$goodmem_dir/docker-compose.debug.yml"
    fi
    
    if [[ "$platform" == "darwin-arm64" ]]; then
        log_debug "Downloading macOS ARM64 overlay for Apple Silicon..."
        dl "$base_url/docker-compose.macos-arm64.yml" "$goodmem_dir/docker-compose.macos-arm64.yml"
    fi
    
    # Database initialization is handled automatically by the server container
    
    # Create .env file for Docker Compose
    log_debug "Creating environment configuration..."
    setup_environment_variables "$goodmem_dir"
    
    # Download maintenance scripts
    log_debug "Downloading maintenance scripts..."
    dl "$base_url/reinstall.sh" "$goodmem_dir/reinstall.sh"
    chmod +x "$goodmem_dir/reinstall.sh"

    dl "$base_url/uninstall.sh" "$goodmem_dir/uninstall.sh"
    chmod +x "$goodmem_dir/uninstall.sh"

    # Validate compose files and environment
    log_debug "Validating configuration..."
    validate_compose_env_refs "$goodmem_dir/docker-compose.yml"
    [[ -f "$goodmem_dir/docker-compose.local-db.yml" ]] && \
        validate_compose_env_refs "$goodmem_dir/docker-compose.local-db.yml"
    validate_env_file "$goodmem_dir/.env"

    log_success "GoodMem files downloaded and configured successfully"
}

# Install CLI binary
install_cli() {
    local platform
    platform=$(detect_platform)
    local base_url="$BASE_URL"
    local cli_dir
    local binary_name="goodmem"
    
    # Set appropriate CLI directory based on platform
    case "$platform" in
        windows-*)
            cli_dir="${CLI_INSTALL_DIR:-$HOME/bin}"
            binary_name="goodmem.exe"
            ;;
        *)
            # Use /var/lib/toolbox on COS systems - it's persistent and executable
            if is_cos; then
                cli_dir="${CLI_INSTALL_DIR:-/var/lib/toolbox}"
            elif [[ "$platform" == wsl*-* ]]; then
                # Use ~/.local/bin for WSL to avoid requiring sudo
                cli_dir="${CLI_INSTALL_DIR:-$HOME/.local/bin}"
            else
                cli_dir="${CLI_INSTALL_DIR:-/usr/local/bin}"
            fi
            ;;
    esac
    
    
    log_info "Installing GoodMem CLI for platform: $platform"
    
    # Ensure CLI directory exists
    if [ ! -d "$cli_dir" ]; then
        log_info "Creating CLI directory: $cli_dir"
        if ! mkdir -p "$cli_dir"; then
            log_error "Failed to create CLI directory: $cli_dir"
            return 1
        fi
    fi
    
    # On COS, ensure /var/lib/toolbox is in PATH
    if is_cos && [[ "$cli_dir" == "/var/lib/toolbox" ]]; then
        if [[ ":$PATH:" != *":/var/lib/toolbox:"* ]]; then
            log_info "Adding /var/lib/toolbox to PATH in ~/.bashrc"
            echo 'export PATH="/var/lib/toolbox:$PATH"' >> ~/.bashrc
            export PATH="/var/lib/toolbox:$PATH"
        fi
    fi
    
    # On WSL, ensure ~/.local/bin is in PATH (WSL may not source ~/.profile)
    if [[ "$platform" == wsl*-* ]] && [[ "$cli_dir" == "$HOME/.local/bin" ]]; then
        if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
            log_info "Adding ~/.local/bin to PATH for current session"
            export PATH="$HOME/.local/bin:$PATH"
        fi
    fi
    
    # Check if we need sudo for installation
    local use_sudo=""
    if [ ! -w "$cli_dir" ]; then
        log_info "Installation directory requires elevated privileges"
        use_sudo="sudo"
    fi
    
    # Determine archive format based on platform
    # Map WSL to appropriate CLI binary (WSL uses Linux binaries)
    local cli_platform="$platform"
    case "$platform" in
        wsl1-*|wsl2-*)
            cli_platform="${platform#wsl?-}"  # wsl2-amd64 -> amd64
            cli_platform="linux-${cli_platform}"  # -> linux-amd64
            ;;
    esac
    
    local archive_file
    case "$cli_platform" in
        windows-*)
            archive_file="goodmem-$cli_platform.zip"
            ;;
        *)
            archive_file="goodmem-$cli_platform.tar.gz"
            ;;
    esac
    
    # Download CLI archive
    log_debug "Downloading CLI archive ($archive_file)..."
    local temp_archive
    temp_archive=$(mktemp)
    temp_files+=("$temp_archive")
    
    if ! dl "$base_url/cli/$archive_file" "$temp_archive"; then
        log_error "Failed to download CLI archive for platform: $platform"
        return 1
    fi
    
    # Extract and install binary
    log_debug "Extracting and installing CLI binary..."
    local temp_dir
    temp_dir=$(mktemp -d)
    temp_dirs+=("$temp_dir")
    
    if ! extract_archive "$temp_archive" "$temp_dir" "$platform"; then
        log_error "Failed to extract CLI archive"
        return 1
    fi
    
    # Find the binary in the extracted files
    # First try executable bit search; if that yields nothing, fall back to name match
    local extracted_binary
    extracted_binary="$(find "$temp_dir" -type f -name 'goodmem*' -perm -111 | head -n1)"
    # Fallback if archive didn't preserve exec bits
    if [ -z "$extracted_binary" ]; then
        extracted_binary="$(find "$temp_dir" -type f -name 'goodmem*' | head -n1)"
    fi
    if [ -z "$extracted_binary" ]; then
        log_error "Could not find goodmem binary in archive"
        return 1
    fi
    
    # Install binary
    log_debug "Installing CLI binary to $cli_dir/$binary_name..."
    if ! $use_sudo install -m 755 "$extracted_binary" "$cli_dir/$binary_name"; then
        log_error "Failed to install CLI binary"
        return 1
    fi
    
    # Verify installation
    if command_exists "$binary_name"; then
        log_success "CLI installed successfully"
        log_info "CLI version: $($binary_name version 2>/dev/null || echo 'unknown')"
    else
        log_warning "CLI installed but not found in PATH. You may need to restart your shell or add $cli_dir to your PATH"
    fi
    
    return 0
}

# Start GoodMem services
start_goodmem() {
    local goodmem_dir="${GOODMEM_DIR:-$HOME/.goodmem}"
    local platform
    
    log_debug "start_goodmem() function called"
    log_debug "In start_goodmem(), USE_REMOTE_DB=$USE_REMOTE_DB"
    
    platform=$(detect_platform)
    
    # Run preflight checks for remote database
    if [[ "$USE_REMOTE_DB" == true ]]; then
        log_info "Running preflight checks for remote database..."
        if ! preflight_database_checks; then
            log_error "Preflight database checks failed. Please fix the issues and try again."
            return 1
        fi
        echo ""
    fi
    
    log_info "Starting GoodMem services..."
    
    # Set Docker platform for Apple Silicon as belt-and-suspenders
    if [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; then
        export DOCKER_DEFAULT_PLATFORM="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}"
    fi
    
    # Change to goodmem directory and start services
    if ! cd "$goodmem_dir"; then
        log_error "Failed to change to directory: $goodmem_dir"
        return 1
    fi
    
    # Detect compose command
    if ! detect_compose; then
        log_error "Failed to detect Docker Compose command"
        return 1
    fi
    
    # Check compose version requirements for remote database
    require_compose_for_remote
    
    # Build compose file arguments
    log_debug "About to call compose_files() function..."
    compose_files "$goodmem_dir" "$platform"
    log_debug "compose_files() function completed"
    
    # Verify required compose files exist
    if [[ ! -f "$goodmem_dir/docker-compose.yml" ]]; then
        log_error "Required docker-compose.yml file not found in $goodmem_dir"
        return 1
    fi
    
    # Debug output to show exactly what command will be executed
    log_debug "Using Docker Compose command: ${COMPOSE_BIN[*]}"
    log_debug "Using compose files: ${COMPOSE_FILES_ARGS[*]}"
    
    log_info "Pulling Docker images..."
    log_debug "About to run: ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} pull"
    if ! "${COMPOSE_BIN[@]}" "${COMPOSE_FILES_ARGS[@]}" pull; then
        log_error "Failed to pull Docker images"
        echo ""
        log_info "This often happens due to Docker registry authentication issues."
        log_info "Try these solutions:"
        echo ""
        echo "1. Clear Docker authentication (recommended):"
        echo "   docker logout ghcr.io"
        echo ""
        echo "2. If the issue persists, try manually pulling the images:"
        echo "   docker pull pgvector/pgvector:pg17"
        echo "   docker pull ghcr.io/pair-systems-inc/goodmem/server:latest"
        echo ""
        log_info "After trying the above, run this installer again."
        exit 1
    fi
    
    # Validate compose configuration before starting services
    log_debug "Validating Docker Compose configuration..."
    if ! "${COMPOSE_BIN[@]}" "${COMPOSE_FILES_ARGS[@]}" config >/dev/null; then
        log_error "Compose config validation failed. See errors above."
        exit 1
    fi
    
    log_info "Starting services (this may take a few minutes)..."
    log_debug "Docker Compose arguments: ${COMPOSE_FILES_ARGS[*]}"
    if "${COMPOSE_BIN[@]}" up --help 2>/dev/null | grep -q -- ' --wait'; then
        log_debug "About to run: ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} up -d --wait --wait-timeout 180"
        "${COMPOSE_BIN[@]}" "${COMPOSE_FILES_ARGS[@]}" up -d --wait --wait-timeout 180
        log_success "GoodMem services started and ready"
    else
        log_warning "'--wait' not supported by your Docker Compose; starting without wait"
        log_debug "About to run: ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} up -d"
        "${COMPOSE_BIN[@]}" "${COMPOSE_FILES_ARGS[@]}" up -d
        log_success "GoodMem services started"
    fi
}

# Setup default OpenAI embedder
setup_default_embedder() {
    # Check if OpenAI setup should be skipped
    if [[ "$SKIP_OPENAI_SETUP" == true ]]; then
        log_info "Skipping OpenAI embedder setup (--no-openai-embedder-registration specified)"
        return 0
    fi
    
    log_info "Setting up default embedder..."
    
    # Check if CLI is available
    if ! command_exists goodmem; then
        log_warning "GoodMem CLI not found in PATH. Skipping embedder setup."
        return 1
    fi
    
    # Check if embedders already exist
    local embedder_list
    if embedder_list=$(goodmem embedder list 2>/dev/null); then
        if ! echo "$embedder_list" | grep -q "No embedders found"; then
            log_info "Embedders already exist. Skipping default embedder setup."
            echo "$embedder_list"
            return 0
        fi
    fi
    
    echo ""
    echo "Default Embedder Setup"
    echo ""
    echo "We can help you set up OpenAI's text-embedding-3-small model (1536 dimensions)."
    echo "This model is compatible with GoodMem's vector storage limits."
    echo ""
    echo "Note: text-embedding-3-large (3072 dimensions) exceeds GoodMem's 1536-dimension limit."
    echo "For other providers or models, use: goodmem embedder create"
    echo ""
    
    # Ask if user wants to set up OpenAI embedder
    printf "Would you like to set up OpenAI text-embedding-3-small? [y/N] (default: N): "
    read -r REPLY </dev/tty
    echo
    # Default to N if empty
    REPLY="${REPLY:-N}"
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        log_info "Skipping OpenAI embedder setup."
        echo "You can create embedders manually with: goodmem embedder create"
        return 0
    fi
    
    # Get OpenAI API key
    echo ""
    echo "Please enter your OpenAI API key (starts with 'sk-'):"
    printf "OpenAI API Key: "
    read -r openai_api_key </dev/tty
    echo
    
    if [[ -z "$openai_api_key" ]]; then
        log_warning "No API key provided. Skipping embedder setup."
        return 1
    fi
    
    if [[ ! "$openai_api_key" =~ ^sk- ]]; then
        log_warning "API key doesn't look like an OpenAI key (should start with 'sk-')"
        log_warning "Continuing anyway..."
    fi
    
    # Create the OpenAI embedder
    log_info "Creating OpenAI text-embedding-3-small embedder..."
    local embedder_output
    if embedder_output=$(goodmem embedder create \
        --endpoint-url "https://api.openai.com/v1" \
        --display-name "OpenAI text-embedding-3-small" \
        --dimensionality 1536 \
        --modality TEXT \
        --model-identifier "text-embedding-3-small" \
        --provider-type OPENAI \
        --distribution-type DENSE \
        --version "1.0.0" \
        --credentials="$openai_api_key" 2>&1); then
        
        log_success "OpenAI embedder created successfully!"
        echo "$embedder_output"
        
        # Extract embedder UUID for potential space creation
        local embedder_uuid
        embedder_uuid=$(echo "$embedder_output" | grep "ID:" | awk '{print $2}')
        
        if [[ -n "$embedder_uuid" ]]; then
            # Ask if user wants to create a default space
            echo ""
            printf "Would you like to create a default space called 'My Memories'? [y/N] (default: N): "
            read -r REPLY </dev/tty
            echo
            # Default to N if empty
            REPLY="${REPLY:-N}"
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                log_info "Creating default space..."
                if goodmem space create --name "My Memories" --embedder-id "$embedder_uuid" 2>/dev/null; then
                    log_success "Default space 'My Memories' created successfully!"
                else
                    log_warning "Failed to create default space. You can create one manually with: goodmem space create"
                fi
            fi
        fi
        
        return 0
    else
        log_error "Failed to create OpenAI embedder:"
        echo "$embedder_output"
        log_warning "You can create embedders manually with: goodmem embedder create"
        return 1
    fi
}

# Wait for server to be ready with retry loop (updated version for testing)
wait_for_server_ready() {
    # Check if parameters are provided (not just empty)
    if [[ $# -eq 0 ]]; then
        # No parameters, use defaults
        local url="http://localhost:8080/health"
        local timeout_seconds="120"
    elif [[ $# -eq 1 ]]; then
        # One parameter provided, check if it's empty
        if [[ -z "$1" ]]; then
            log_error "Usage: wait_for_server_ready <url> <timeout_seconds>"
            return 1
        fi
        local url="$1"
        local timeout_seconds="120"
    elif [[ $# -eq 2 ]]; then
        # Two parameters provided, check if either is empty
        if [[ -z "$1" || -z "$2" ]]; then
            log_error "Usage: wait_for_server_ready <url> <timeout_seconds>"
            return 1
        fi
        local url="$1"
        local timeout_seconds="$2"
    else
        log_error "Usage: wait_for_server_ready <url> <timeout_seconds>"
        return 1
    fi
    
    local count=0
    local max_attempts=$((timeout_seconds / 2))
    
    while (( count < max_attempts )); do
        if curl -fsS "$url" >/dev/null 2>&1; then
            echo "Server is ready"
            return 0
        fi
        echo "Waiting for server to be ready (attempt $((count + 1))/$max_attempts)..."
        sleep 2
        ((count++))
    done
    
    log_error "Server did not become ready within $timeout_seconds seconds"
    return 1
}

# Test helper functions for bats compatibility

# Check Docker Compose version (test-friendly version)
check_docker_compose_version() {
    # Determine which compose command to use
    local compose_cmd
    if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
        compose_cmd="docker compose"
    elif command -v docker-compose >/dev/null 2>&1; then
        compose_cmd="docker-compose"
    else
        echo "Failed to get Docker Compose version"
        return 1
    fi
    
    # Get the version
    local have
    have=$($compose_cmd version 2>/dev/null | grep -oE '([0-9]+\.)+[0-9]+' | head -n1)
    
    if [[ -z "$have" ]]; then
        echo "Failed to get Docker Compose version"
        return 1
    fi
    
    # Check if version is >= 2.20.2
    if printf '%s\n%s\n' "2.20.2" "$have" | sort -V | tail -n1 | grep -q "^$have"; then
        return 0
    else
        echo "Docker Compose version 2.20.2 or newer is required"
        echo "Found: v$have"
        return 1
    fi
}

# Build compose arguments for the given mode
build_compose_args() {
    if [[ "$USE_REMOTE_DB" == true ]]; then
        echo "-f docker-compose.yml -f docker-compose.remote-db.yml"
    else
        echo "-f docker-compose.yml"
    fi
}

# Build database URL from parts
build_db_url() {
    local host="${DB_HOST}"
    local port="${DB_PORT:-5432}"
    local dbname="${DB_NAME}"
    
    # Handle IPv6 addresses
    if [[ "$host" =~ : ]] && [[ ! "$host" =~ ^\[.*\]$ ]]; then
        host="[$host]"
    fi
    
    echo "jdbc:postgresql://${host}:${port}/${dbname}"
}

# Build database URL with SSL parameters
build_db_url_with_ssl_params() {
    local base_url
    base_url=$(build_db_url)
    local ssl_mode="${DB_SSL_MODE:-require}"
    
    echo "${base_url}?sslmode=${ssl_mode}"
}

# Validate remote database flags (wrapper for existing function)
validate_remote_db_flags() {
    if [[ -z "$DB_HOST" ]]; then
        echo "Error: DB_HOST is required for remote database"
        return 1
    fi
    if [[ -z "$DB_USER" ]]; then
        echo "Error: DB_USER is required for remote database"
        return 1
    fi
    if [[ -z "$DB_PASSWORD" ]]; then
        echo "Error: DB_PASSWORD is required for remote database"
        return 1
    fi
    if [[ -z "$DB_NAME" ]]; then
        echo "Error: DB_NAME is required for remote database"
        return 1
    fi
    
    # Check for empty or whitespace-only values
    if [[ -z "${DB_HOST// }" ]]; then
        echo "Error: DB_HOST cannot be empty or whitespace"
        return 1
    fi
    
    return 0
}

# Write environment file
write_env_file() {
    local env_file="$1"
    
    if [[ -z "$env_file" ]]; then
        echo "Error: Environment file path is required"
        return 1
    fi
    
    # Create parent directory if needed
    local dir
    dir=$(dirname "$env_file")
    mkdir -p "$dir"
    
    if [[ "$USE_REMOTE_DB" == true ]]; then
        # Validate required variables for remote mode
        if [[ -z "$DB_URL" || -z "$DB_USER" || -z "$DB_PASSWORD" ]]; then
            echo "Error: DB_URL, DB_USER, and DB_PASSWORD are required for remote database"
            return 1
        fi
        
        # Write remote database configuration
        cat > "$env_file" <<EOF
DB_URL=$DB_URL
DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD
DATA_DIR_BASE=${DATA_DIR_BASE:-$HOME/.goodmem/data}
EOF
    else
        # Validate required variables for local mode
        if [[ -z "$POSTGRES_USER" || -z "$POSTGRES_PASSWORD" || -z "$POSTGRES_DB" ]]; then
            echo "Error: POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB are required for local database"
            return 1
        fi
        
        # Write local database configuration
        cat > "$env_file" <<EOF
POSTGRES_USER=$POSTGRES_USER
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
POSTGRES_DB=$POSTGRES_DB
DATA_DIR_BASE=${DATA_DIR_BASE:-$HOME/.goodmem/data}
EOF
    fi
}

# Preflight check for remote database
preflight_check_remote_db() {
    if [[ -z "$DB_URL_DSN" ]]; then
        echo "Error: DB_URL_DSN not set"
        return 1
    fi
    
    if ! command -v psql >/dev/null 2>&1; then
        echo "Error: psql command not found. Please install PostgreSQL client tools."
        return 1
    fi
    
    echo "Testing database connection..."
    if ! psql "$DB_URL_DSN" -c "SELECT 1" >/dev/null 2>&1; then
        echo "Database connection failed"
        return 1
    fi
    
    echo "Checking required extensions..."
    if ! psql "$DB_URL_DSN" -c "SELECT 1 FROM pg_extension WHERE extname = 'uuid-ossp'" >/dev/null 2>&1; then
        echo "Required database extension not available: uuid-ossp"
        return 1
    fi
    
    echo "Checking database permissions..."
    if ! psql "$DB_URL_DSN" -c "CREATE SCHEMA IF NOT EXISTS test_permissions; DROP SCHEMA test_permissions" >/dev/null 2>&1; then
        echo "Insufficient database permissions: cannot create schemas"
        return 1
    fi
    
    echo "Database connection successful"
    return 0
}

# Initialize GoodMem system
initialize_goodmem_system() {
    log_info "Initializing GoodMem system..."
    
    # Check if CLI is available
    if ! command_exists goodmem; then
        log_warning "GoodMem CLI not found in PATH. Skipping system initialization."
        log_warning "You can initialize the system manually by running: goodmem init"
        return 1
    fi
    
    # Wait for server readiness
    if ! wait_for_server_ready; then
        log_error "Server readiness check failed"
        return 1
    fi
    
    # Run goodmem init and capture output
    local init_output
    if init_output=$(goodmem init 2>&1); then
        log_success "System initialized successfully!"
        
        # Extract and display the important information
        echo ""
        echo "🔑 System Initialization Complete!"
        echo ""
        echo "$init_output"
        echo ""
        log_warning "IMPORTANT: Save the root API key securely. It will not be shown again."
        echo ""
        
        return 0
    else
        log_error "Failed to initialize GoodMem system:"
        echo "$init_output"
        log_warning "You can initialize the system manually by running: goodmem init"
        return 1
    fi
}

# Verify the installation
verify_installation() {
    local goodmem_dir="${GOODMEM_DIR:-$HOME/.goodmem}"
    local platform
    
    detect_compose || return 1
    platform=$(detect_platform)
    compose_files "$goodmem_dir" "$platform"
    
    log_debug "Verifying installation..."
    
    # Test server endpoint
    log_debug "Testing server connectivity..."
    
    if command_exists curl; then
        if curl -sSf http://localhost:8080/health >/dev/null 2>&1 || \
           curl -sSI http://localhost:8080 >/dev/null 2>&1; then
            log_success "Server is responding on port 8080"
        else
            log_warning "Server may still be starting. Check with: ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} logs server"
        fi
    fi
    
    log_success "GoodMem installation complete!"
    echo ""
    echo "🎉 GoodMem is now running!"
    echo ""
    echo "Access points:"
    echo "  • REST API: http://localhost:8080"
    echo "  • gRPC API: localhost:9090"
    if [[ "$INSTALL_MODE" == "debug" ]]; then
        echo "  • Database: localhost:5432 (debug mode)"
        echo "  • JobRunr Dashboard: http://localhost:8001"
    else
        echo "  • Database: Internal only (production mode)"
    fi
    echo ""
    echo "Management commands:"
    echo "  • View logs:    cd $goodmem_dir && ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} logs -f"
    echo "  • Stop:         cd $goodmem_dir && ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} down"
    echo "  • Restart:      cd $goodmem_dir && ${COMPOSE_BIN[*]} ${COMPOSE_FILES_ARGS[*]} restart"
    echo ""
}

# Main installation function
main() {
    # Parse command line arguments first
    parse_arguments "$@"

    # Validate mutually exclusive flags
    if [[ "$USE_LOCAL_DB" == true && "$USE_REMOTE_DB" == true ]]; then
        log_error "Cannot use both --local-db and --remote-db options together"
        exit 1
    fi

    show_logo
    
    log_info "Starting GoodMem installation..."
    log_info "Platform: $(detect_platform)"
    
    # Debug: Show remote database configuration status
    log_debug "USE_REMOTE_DB=$USE_REMOTE_DB"
    log_debug "DB_HOST=${DB_HOST:-<unset>}"
    log_debug "DB_USER=${DB_USER:-<unset>}"
    log_debug "DB_NAME=${DB_NAME:-<unset>}"

    # Check whether ports that Goodmem services use are available
    # ports to check: 8080 (server) and 9090 (gRPC)
    # Ports 5432 and 8001 are only used in debug mode. so we don't check them here.
    for port in 8080 9090; do
        check_port_availability "$port" "Port $port is used by another application. Please free it and try again."
    done
    
    # Prompt for installation mode if not specified via command line
    if [[ -z "$INSTALL_MODE" ]]; then
        if can_prompt; then
            prompt_installation_mode
        else
            INSTALL_MODE="prod"
            log_info "Non-interactive environment detected; defaulting to production mode"
        fi
    fi
    
    # Force remote database on Container-Optimized OS
    if [[ "$USE_REMOTE_DB" != true ]] && is_cos; then
        log_warning "Container-Optimized OS detected: Local PostgreSQL database is not supported"
        log_warning "COS has strict security policies that prevent proper database container operation"
        echo ""
        log_info "Switching to remote PostgreSQL database configuration..."
        USE_REMOTE_DB=true
        
        # Check if database credentials were provided via command line
        if [[ -z "$DB_URL_DSN" && (-z "$DB_HOST" || -z "$DB_USER" || -z "$DB_PASSWORD" || -z "$DB_NAME") ]]; then
            if can_prompt; then
                # Prompt for database details since none were provided
                prompt_remote_database_details
            else
                # Non-interactive environment without credentials
                echo ""
                echo "Remote PostgreSQL database credentials are required on COS."
                echo "Please re-run with database connection details:"
                echo "  --db-url 'postgresql://user:pass@host:5432/dbname'"
                echo "or individual flags: --db-host, --db-user, --db-password, --db-name"
                exit 1
            fi
        fi
        echo ""
    fi
    
    # Prompt for database configuration if not specified via command line
    if [[ "$USE_REMOTE_DB" != true ]]; then
        if can_prompt; then
            prompt_database_configuration
        else
            log_info "Non-interactive environment detected; defaulting to local database"
        fi
    fi
    
    # Validate remote database configuration
    validate_remote_db_config
    
    # Check and install Docker if needed
    if ! check_docker; then
        log_info "Docker installation required..."
        install_docker
        
        # Verify Docker installation
        if ! check_docker; then
            log_error "Docker installation failed or Docker daemon is not running"
            log_error "Please install Docker manually and ensure it's running"
            exit 1
        fi
    fi
    
    log_success "Docker is ready!"
    
    # Download GoodMem files
    download_goodmem_files
    
    # Install CLI
    if ! install_cli; then
        log_warning "CLI installation failed, but continuing with server installation"
        log_warning "You can install the CLI manually from: https://github.com/PAIR-Systems-Inc/goodmem/releases"
    fi
    
    # Start GoodMem services
    log_debug "About to call start_goodmem() from main..."
    start_goodmem
    log_debug "start_goodmem() call completed"
    
    # Verify installation
    verify_installation
    
    # Initialize GoodMem system and setup embedder if successful
    if initialize_goodmem_system; then
        setup_default_embedder
    fi
    
    # Show next steps at the very end
    echo ""
    echo "📚 Next Steps:"
    echo "  • Getting Started Guide: https://docs.goodmem.ai/docs/get-started/nextsteps/"
    echo "  • API Reference: https://docs.goodmem.ai/docs/reference/api-reference/"
    echo ""
}

# Run main function only when executed directly (not when sourced for testing)
if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then
    main "$@"
fi
