#!/bin/bash
# pipefail so silent pipe failures (e.g. `bunx convex env list | sed ...`)
# surface instead of producing empty downstream values. -u is intentionally
# omitted to avoid breaking the many `${VAR:-default}` patterns.
set -eo pipefail

# ============================================================================
# Tale Platform Docker Entrypoint (Phase 2: split architecture)
# ----------------------------------------------------------------------------
# Platform is a Vite/TanStack Start frontend + HTTP server. It acts as a
# Convex client: on startup it pushes env vars and function code to the
# sibling `convex` service (see services/convex/).
#
# Responsibilities:
#   1. Privilege drop (root → uid 1001 app)
#   2. Env normalization (incl. ensure_instance_secret)
#   3. Wait for the convex service to be reachable (http://convex:3210/version)
#   4. Deploy Convex functions + sync env vars (push model; three-stage error
#      classification on failure)
#   5. Start Vite server (`bun server.ts`)
#   6. Touch /tmp/platform-ready (compose healthcheck gate)
#   7. Graceful shutdown on SIGTERM
#
# NOT done here any more (owned by convex service):
#   - convex-local-backend daemon
#   - Convex Dashboard
#   - Builtin JSON seed
#   - CA certificate trust for the Rust backend
#   - monitor_convex crash loop
# ============================================================================

# ----------------------------------------------------------------------------
# Logging helpers
# ----------------------------------------------------------------------------
log_info()    { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ℹ️  $*"; }
log_ok()      { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ✅ $*"; }
log_warn()    { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️  $*" >&2; }
log_error()   { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ❌ $*" >&2; }
log_section() { echo; echo "════════════════════════════════════"; echo "  $*"; echo "════════════════════════════════════"; }

# ----------------------------------------------------------------------------
# Privilege handling: platform no longer owns /app/data. The only thing the
# re-exec dance still does is make sure we run as the app user so that Bun
# picks up the right HOME etc. Volume ownership is now the convex container's
# problem.
# ----------------------------------------------------------------------------
if [ "$(id -u)" = '0' ]; then
  exec gosu app "$0" "$@"
fi

log_section "Tale Platform starting (version ${TALE_VERSION:-unknown})"

# ============================================================================
# Shutdown handling
# ============================================================================
SHUTDOWN_MARKER="/tmp/platform-shutting-down"
READY_MARKER="/tmp/platform-ready"
rm -f "$SHUTDOWN_MARKER" "$READY_MARKER"

VITE_PID=""

shutdown() {
  log_section "Platform graceful shutdown"
  touch "$SHUTDOWN_MARKER"

  local drain_wait="${SHUTDOWN_DRAIN_SECONDS:-6}"
  log_info "Draining ${drain_wait}s for load balancer to stop routing..."
  sleep "$drain_wait"

  local grace_period="${SHUTDOWN_GRACE_SECONDS:-5}"
  log_info "Waiting ${grace_period}s for in-flight requests..."
  sleep "$grace_period"

  log_info "Sending SIGTERM to Vite server..."
  kill -TERM "$VITE_PID" 2>/dev/null || true

  local shutdown_timeout="${SHUTDOWN_TIMEOUT_SECONDS:-30}"
  local waited=0
  while [ "$waited" -lt "$shutdown_timeout" ]; do
    if ! kill -0 "$VITE_PID" 2>/dev/null; then
      break
    fi
    sleep 1
    waited=$((waited + 1))
  done

  if [ "$waited" -ge "$shutdown_timeout" ]; then
    log_warn "Timeout reached; force killing Vite"
    kill -KILL "$VITE_PID" 2>/dev/null || true
  fi

  rm -f "$SHUTDOWN_MARKER" "$READY_MARKER"
  log_ok "Platform stopped gracefully"
  exit 0
}
trap shutdown SIGTERM SIGINT

# ============================================================================
# Environment normalization
# ============================================================================
source "$(dirname "$0")/env.sh"
env_normalize_common
ensure_instance_secret

echo "🔍 Environment after normalization:"
echo "   HOST=${HOST}"
echo "   SITE_URL=${SITE_URL}"
echo "   PORT=${PORT}"
echo "   CONVEX_URL=${CONVEX_URL:-http://convex:3210}"

# Export auth / encryption / rag env vars that platform itself uses
# (the rest get pushed to Convex via `convex env set` below).
export BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET}"
export BETTER_AUTH_URL="${BETTER_AUTH_URL}"
export ENCRYPTION_SECRET_HEX="${ENCRYPTION_SECRET_HEX}"

# Default RAG DB URL constructed by env_normalize_common.
if [ -z "${RAG_DATABASE_URL:-}" ] && [ -n "${POSTGRES_URL:-}" ]; then
  export RAG_DATABASE_URL="${POSTGRES_URL}/tale_knowledge"
fi

# Admin key + WebDAV HMAC key. Both must be readable from `bun server.ts`'s
# process.env, not just the deploy_convex_functions function. `ADMIN_KEY`
# was previously a `local` and silently went out of scope before the
# platform Hono server started — every `/dav/*` request then 500'd. Both
# are deterministic from $INSTANCE_SECRET so they survive restarts and
# operators don't need to set them by hand.
#
# `WEBDAV_APP_PASSWORD_HMAC_KEY` accepts an explicit override (.env) for
# operators who want a key rotation independent of INSTANCE_SECRET. When
# unset, we derive a 64-char hex via sha256(secret || ':webdav-hmac:v1').
export ADMIN_KEY="$(generate_key "$INSTANCE_NAME" "$INSTANCE_SECRET")"
if [ -z "${WEBDAV_APP_PASSWORD_HMAC_KEY:-}" ]; then
  WEBDAV_APP_PASSWORD_HMAC_KEY="$(printf '%s' "${INSTANCE_SECRET}:webdav-hmac:v1" | sha256sum | awk '{print $1}')"
fi
export WEBDAV_APP_PASSWORD_HMAC_KEY

# ============================================================================
# Helpers
# ============================================================================
wait_for_http() {
  local url="$1" timeout="${2:-60}" name="${3:-Service}" allow_timeout="${4:-false}"
  log_info "Waiting for ${name} at ${url}..."
  local counter=0
  until curl -sf "$url" > /dev/null 2>&1; do
    counter=$((counter + 1))
    if [ $counter -gt $timeout ]; then
      if [ "$allow_timeout" = "true" ]; then
        log_warn "${name} health-check timeout (continuing)"
        return 0
      fi
      log_error "${name} failed to respond within ${timeout}s"
      return 1
    fi
    sleep 1
  done
  log_ok "${name} is reachable"
}

dump_diagnostics() {
  local ctx="$1"
  echo
  echo "──────── 🔍 Diagnostics: $ctx ────────"
  echo "  Timestamp:   $(date -Iseconds)"
  echo "  Hostname:    $(hostname)"
  echo "  CONVEX_URL:  ${CONVEX_URL}"
  echo "  Memory:      $(free -h 2>/dev/null | awk '/^Mem:/ {print $3" / "$2}')"
  echo "──────────────────────────────────────"
  echo
}

# ============================================================================
# Deploy Convex functions (remote push to convex:3210)
# ----------------------------------------------------------------------------
# This is the core Phase 2 change: we run `bunx convex deploy` against the
# sibling convex service, not a local backend. Env vars are synced first so
# functions can read them at runtime (Convex persists them in its own DB).
# ============================================================================
CONVEX_URL="${CONVEX_URL:-http://convex:3210}"
CONVEX_DEPLOY_TIMEOUT="${CONVEX_DEPLOY_TIMEOUT:-300}"

# Denylist of env vars that should NOT be synced to Convex. Initially
# empty — we wire the mechanism but ship no entries. Add a name here
# only when a specific var is shown to actively break Convex (e.g.
# conflicts with a same-named convex-side var that needs a different
# value, or causes a deploy failure). Most additions to this list
# should NOT be needed.
#
# Mirrors `bun dev`'s sync-convex-env-from-dotenv.ts behavior: push
# everything we see, exclude only what's known-incompatible.
ENV_SYNC_DENYLIST=()

deploy_convex_functions() {
  log_section "Deploying Convex functions (remote push to ${CONVEX_URL})"

  if [ ! -d "/app/convex" ]; then
    log_warn "No /app/convex directory found, skipping function deployment"
    return 0
  fi

  # Force TALE_CONFIG_DIR to the convex container's internal mount point.
  # The `.env` file may contain a host-side value (e.g.
  # `/home/you/tale/examples`) left over from running `bun scripts/dev.ts`
  # on the host — that path is unreachable inside the convex container.
  #
  # Only TALE_CONFIG_DIR is pushed; AGENTS_DIR/WORKFLOWS_DIR/INTEGRATIONS_DIR/
  # PROVIDERS_DIR are derived inside Convex (`convex/*/file_utils.ts` falls
  # back to `${TALE_CONFIG_DIR}/<subdir>` when the specific var is absent).
  #
  # For local dev against real `examples/` edits, bind-mount the host
  # examples dir into the convex container via compose.dev.yml:
  #   convex:
  #     volumes:
  #       - ./examples:/app/data
  export TALE_CONFIG_DIR=/app/data

  # 1. Wait for the convex service to accept HTTP.
  if ! wait_for_http "${CONVEX_URL}/version" 120 "Convex service /version" false; then
    log_error "Convex service is not reachable. Is the \`convex\` container running?"
    dump_diagnostics "Convex unreachable"
    exit 1
  fi

  # 2. Give search-index bootstrap a moment to settle before push.
  log_info "Waiting 10s for search-index workers to initialize..."
  sleep 10

  # 3. ADMIN_KEY is already exported at module scope (see env section above)
  # so `bun server.ts` inherits it. Used here for the `bunx convex env set` +
  # `convex deploy` CLI calls below.

  # 4. Fetch current Convex env vars to compute a diff.
  export HOME=/home/app
  log_info "Fetching current Convex env vars..."
  local CONVEX_ENV_OUTPUT
  CONVEX_ENV_OUTPUT=$(bunx convex env list --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" 2>/dev/null || echo "")

  declare -A CONVEX_ENV_MAP
  while IFS= read -r line; do
    [ -z "$line" ] && continue
    local key="${line%%=*}"
    [ "$key" = "$line" ] && continue
    CONVEX_ENV_MAP["$key"]="${line#*=}"
  done <<< "$CONVEX_ENV_OUTPUT"

  # Unconditional purge: the per-domain env overrides (AGENTS_DIR /
  # WORKFLOWS_DIR / INTEGRATIONS_DIR / PROVIDERS_DIR / SKILLS_DIR) are no
  # longer honored by the resolvers under the uniform org-first layout.
  # Remove them from the Convex deployment env on every boot, regardless
  # of whether they look auto-derived or operator-customized. Operators
  # who previously relied on a custom value must now point TALE_CONFIG_DIR
  # at the root and use the `<orgSlug>/<domain>/` subtree.
  local -a LEGACY_DOMAIN_VARS=(
    AGENTS_DIR
    WORKFLOWS_DIR
    INTEGRATIONS_DIR
    PROVIDERS_DIR
    SKILLS_DIR
  )
  for legacy in "${LEGACY_DOMAIN_VARS[@]}"; do
    if [ "${CONVEX_ENV_MAP[$legacy]+_}" ]; then
      # Match the surrounding env-sync loop's aggregation pattern: track
      # failures in `failed_vars` later, never swallow with `>/dev/null`
      # so a real CLI error doesn't leave the legacy var lingering in
      # Convex without an operator-visible signal.
      if bunx convex env remove "$legacy" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" >/dev/null; then
        echo "   ✓ $legacy removed (no longer honored under org-first layout)"
        unset 'CONVEX_ENV_MAP[$legacy]'
      else
        log_warn "Failed to remove legacy env var $legacy from Convex; will retry on next boot"
      fi
    fi
  done

  # 5. Push every env var the platform container sees to Convex (denylist
  # semantics — only skip names listed in ENV_SYNC_DENYLIST or names
  # that exceed the 40-char Convex limit). Matches the local-dev sync
  # script's behavior so prod ↔ dev have the same env-propagation
  # surface, and operators can use any env name they want without
  # negotiating a platform-side allowlist.
  local sync_count=0 skip_count=0 unchanged_count=0 remove_count=0
  local failed_vars=()
  local seen_vars=()

  is_denylisted() {
    local name="$1"
    local entry
    for entry in "${ENV_SYNC_DENYLIST[@]}"; do
      [ "$name" = "$entry" ] && return 0
    done
    return 1
  }

  while IFS='=' read -r var_name var_value; do
    [ -z "$var_name" ] && continue
    [ -z "$var_value" ] && continue
    if is_denylisted "$var_name"; then
      skip_count=$((skip_count + 1))
      continue
    fi
    if [ ${#var_name} -gt 40 ]; then
      log_warn "Skipping $var_name: name exceeds Convex 40-char limit"
      skip_count=$((skip_count + 1))
      continue
    fi
    seen_vars+=("$var_name")
    if [ "${CONVEX_ENV_MAP[$var_name]+_}" ] && [ "${CONVEX_ENV_MAP[$var_name]}" = "$var_value" ]; then
      unchanged_count=$((unchanged_count + 1))
      continue
    fi
    local change_type="updated"
    [ -z "${CONVEX_ENV_MAP[$var_name]+_}" ] && change_type="new"
    if bunx convex env set "$var_name" "$var_value" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" >/dev/null 2>&1; then
      sync_count=$((sync_count + 1))
      echo "   ✓ $var_name ($change_type)"
    else
      failed_vars+=("$var_name")
      log_warn "Failed to set $var_name (value length: ${#var_value})"
    fi
  done < <(env)

  # 5b. Remove vars from Convex that are no longer set on the platform.
  # Without this, env vars unset on the platform side linger in Convex.
  # Skip orphans we already removed above (in the LEGACY_DOMAIN_VARS block).
  for convex_var in "${!CONVEX_ENV_MAP[@]}"; do
    local found=false
    local sv
    for sv in "${seen_vars[@]}"; do
      if [ "$sv" = "$convex_var" ]; then found=true; break; fi
    done
    if [ "$found" = "false" ]; then
      if is_denylisted "$convex_var"; then continue; fi
      if bunx convex env remove "$convex_var" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" >/dev/null 2>&1; then
        remove_count=$((remove_count + 1))
        echo "   ✓ $convex_var (removed; unset on platform)"
      else
        failed_vars+=("$convex_var")
        log_warn "Failed to remove $convex_var"
      fi
    fi
  done

  if [ ${#failed_vars[@]} -gt 0 ]; then
    log_warn "Failed env vars:"
    for v in "${failed_vars[@]}"; do
      # `${#v}` is the name length. Indirect value-length lookup
      # (`${#!v}`) is invalid bash and would crash this loop under
      # `set -eo pipefail`, taking the entrypoint with it.
      echo "    - $v (name length: ${#v})"
    done
    echo "  Possible causes: name > 40 chars / value > 8 KB / invalid characters"
  fi

  if [ $sync_count -eq 0 ] && [ $remove_count -eq 0 ] && [ $unchanged_count -gt 0 ]; then
    echo "   ⏭️  All $unchanged_count env vars unchanged"
  else
    echo "   ✅ Synced $sync_count, removed $remove_count, unchanged $unchanged_count, skipped $skip_count"
  fi

  # 6. Deploy functions (three-stage error classification below).
  log_info "Running convex deploy (timeout ${CONVEX_DEPLOY_TIMEOUT}s)..."
  sleep 2  # Avoid RaceDetected (env sync race)

  local deploy_log
  deploy_log=$(mktemp)
  local deploy_exit=0

  timeout "$CONVEX_DEPLOY_TIMEOUT" bunx convex deploy \
    --url "$CONVEX_URL" \
    --admin-key "$ADMIN_KEY" \
    --typecheck disable --yes 2>&1 | tee "$deploy_log" || deploy_exit=$?

  if [ $deploy_exit -eq 0 ]; then
    log_ok "Convex functions deployed successfully"
    rm -f "$deploy_log"

    # Run Convex data migrations. Non-fatal: each migration is idempotent,
    # so a transient failure here is retried on the next platform boot and
    # must not prevent the platform from serving.
    log_info "Running Convex data migrations..."
    local migrations_exit=0
    timeout 600 bunx convex run migrations:runAll \
      --url "$CONVEX_URL" \
      --admin-key "$ADMIN_KEY" 2>&1 || migrations_exit=$?
    if [ $migrations_exit -eq 0 ]; then
      log_ok "Convex data migrations complete"
    else
      log_error "Convex data migrations failed (exit code: $migrations_exit) — platform will continue; legacy data may need manual backfill."
    fi

    return 0
  fi

  # --- Failure classification (three-stage) ---
  log_error "Convex deploy failed (exit code: $deploy_exit)"
  echo
  echo "━━━ Error diagnosis ━━━"

  local retry=false

  if [ $deploy_exit -eq 124 ]; then
    # wait_for_schema stage
    log_error "Reason: timeout (${CONVEX_DEPLOY_TIMEOUT}s)"
    echo "  → Most likely stuck in wait_for_schema (search-index backfill)."
    if grep -q "Backfilling indexes" "$deploy_log"; then
      echo "  ✔ Confirmed: deploy blocked on index backfill."
      grep "Backfilling indexes" "$deploy_log" | tail -1 | sed 's/^/  last progress: /'
    fi
    if grep -q "TextLiveFlusher died" "$deploy_log"; then
      echo "  ⚠️  TextLiveFlusher errors detected in convex logs."
      echo "     docker compose logs convex | grep TextLiveFlusher"
    fi
    echo "  fix: inspect convex-data /app/data/convex/search; see plan section."

  elif grep -q "RaceDetected" "$deploy_log"; then
    log_error "Reason: RaceDetected (env vars modified mid-push)"
    echo "  fix: check for parallel deploys; retry will happen automatically."
    retry=true

  elif grep -q "ConcurrentPush" "$deploy_log"; then
    log_error "Reason: ConcurrentPush (another deploy in progress)"
    echo "  fix: ps -ef | grep convex; ensure only one platform color is pushing."

  elif grep -q "ModulesTooLarge" "$deploy_log"; then
    log_error "Reason: compiled modules exceed the 45 MB gzip limit"
    echo "  fix: du -sh /app/convex; prune unused deps; move big libs behind \"use node\""

  elif grep -q "InvalidSchema" "$deploy_log"; then
    log_error "Reason: schema conflicts with existing data"
    echo "  fix: migrate data first, or make the new field optional."

  elif grep -q "TextIndexTooLarge\|VectorIndexTooLarge" "$deploy_log"; then
    log_error "Reason: search or vector index exceeds memory limit (default 100 MiB)"
    echo "  fix: raise SEARCH_INDEX_SIZE_HARD_LIMIT or reduce index scope."

  elif grep -q "SearchIndexesUnavailable\|VectorIndexesUnavailable" "$deploy_log"; then
    log_error "Reason: search indexes not yet bootstrapped on convex side"
    echo "  → Cold boot: index workers take 30–90s to come up; will back off and retry."
    retry=true
    # Use a bigger backoff specifically for index bootstrap; the standard 10s
    # is rarely enough on first boot.
    SEARCH_INDEX_RETRY_BACKOFF=45

  elif grep -q "AuthConfigMissingEnvironmentVariable" "$deploy_log"; then
    local missing_var
    missing_var=$(grep -oP 'Environment variable \K\w+' "$deploy_log" | head -1)
    log_error "Reason: auth.config.ts references unset env var"
    [ -n "$missing_var" ] && echo "  missing: $missing_var"
    echo "  fix: add it to ENV_VARS_TO_SYNC in this entrypoint + ensure it is exported."

  elif grep -qi "fetch failed\|ECONNREFUSED\|ETIMEDOUT" "$deploy_log"; then
    log_error "Reason: network/connection issue to ${CONVEX_URL}"
    echo "  fix: check convex container health; check docker network."
    retry=true

  elif grep -qi "invalid admin key\|unauthorized" "$deploy_log"; then
    log_error "Reason: admin key invalid"
    echo "  fix: check INSTANCE_NAME and INSTANCE_SECRET match on both services."

  else
    log_error "Reason: unclassified. See full deploy log above."
    echo "  fix: try RUST_LOG=debug on the convex service and re-run deploy."
  fi

  echo
  dump_diagnostics "Convex deploy failure"

  if [ "$retry" = "true" ]; then
    # Up to 3 attempts with exponential backoff (capped); the first sleep is
    # the classification-specific backoff if set (search-index bootstrap),
    # otherwise the default 10s.
    local backoff="${SEARCH_INDEX_RETRY_BACKOFF:-10}"
    local attempt
    for attempt in 1 2 3; do
      log_warn "Retryable error detected; sleeping ${backoff}s before attempt ${attempt}/3..."
      sleep "$backoff"
      if timeout "$CONVEX_DEPLOY_TIMEOUT" bunx convex deploy \
          --url "$CONVEX_URL" \
          --admin-key "$ADMIN_KEY" \
          --typecheck disable --yes 2>&1 | tee -a "$deploy_log"; then
        log_ok "Convex functions deployed on retry attempt ${attempt}"
        rm -f "$deploy_log"
        return 0
      fi
      # Cap at ~90s to avoid runaway waits but still cover the longest
      # observed search-index bootstrap.
      backoff=$(( backoff * 2 ))
      [ $backoff -gt 90 ] && backoff=90
    done
    log_error "All retries failed"
  fi

  rm -f "$deploy_log"
  exit 1
}

deploy_convex_functions

# ============================================================================
# Vite application
# ============================================================================
log_section "Starting Vite server on port ${PORT}"

export SENTRY_RELEASE="${TALE_VERSION:-unknown}"

bun server.ts &
VITE_PID=$!

wait_for_http "http://localhost:${PORT}/api/health" 30 "Vite server" true

# ============================================================================
# Readiness marker (compose healthcheck gate)
# ============================================================================
touch "$READY_MARKER"
log_ok "Platform ready (marker: $READY_MARKER)"

# ============================================================================
# Derived display URLs
# ============================================================================
DISPLAY_BASE_URL="${SITE_URL:-http://localhost:${PORT}}${BASE_PATH:-}"

echo
echo "🎉 Tale Platform is running!"
echo
echo "   📱 Application:       ${DISPLAY_BASE_URL}"
echo "   🔌 Convex API (WS):   ${DISPLAY_BASE_URL}/ws_api"
echo "   ⚡ Convex Actions:     ${DISPLAY_BASE_URL}/http_api"
echo "   📊 Convex Dashboard:  ${DISPLAY_BASE_URL}/convex-dashboard"
echo

wait "$VITE_PID"
