#!/bin/bash
# Session startup hook for Claude Code.
# - Remote (web) sessions: installs tools, generates code + CSS, brings up
#   Postgres with dev + test DBs, mints/persists ENCRYPTION_KEY, builds
#   bin/breadbox, applies migrations, seeds an admin, and writes
#   DATABASE_URL / ENCRYPTION_KEY / SERVER_PORT / BB_USER / BB_PASS into
#   CLAUDE_ENV_FILE so `bin/breadbox serve` works in one command.
# - Local worktree sessions: verifies build and injects env vars
#   (file copying is handled by .worktreeinclude)

PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
cd "$PROJECT_DIR"

# --- Local sessions ---
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
  # Reap orphaned dev servers left by previously-closed sessions (dead pids,
  # removed worktrees). Cheap + safe; runs for both main-repo and worktree
  # sessions so cleanup happens regardless of where you start. The shared lib
  # is a committed file, so it's present in every checkout/worktree.
  if [ -f "$PROJECT_DIR/scripts/dev-lib.sh" ]; then
    # shellcheck source=scripts/dev-lib.sh
    . "$PROJECT_DIR/scripts/dev-lib.sh"
    bb_reap 2>/dev/null || true
  fi

  # Check if we're in a git worktree
  GIT_COMMON="$(git rev-parse --git-common-dir 2>/dev/null || echo ".git")"
  if [ "$GIT_COMMON" = ".git" ]; then
    # Main repo — nothing else to do
    exit 0
  fi

  # Worktree: verify build works with the copied artifacts.
  # .worktreeinclude copies sqlc and templ generated files from the main repo,
  # but they're gitignored and may not exist there (or may be stale against
  # this worktree's branch). If the build fails, regenerate both.
  echo "==> Worktree session — verifying Go build..."
  if ! go build ./... 2>&1; then
    # templ first — components are a lower dependency layer than admin/api
    # packages, and stale/missing *_templ.go is a common cause of the build
    # break. templ generate is fast (~100ms) and idempotent.
    if command -v templ &>/dev/null; then
      echo "    Build failed — regenerating templ files..."
      if templ generate 2>&1; then
        echo "    templ regenerated"
      else
        echo "WARN: templ generate failed"
      fi
    else
      echo "WARN: templ not available, skipping templ regenerate"
    fi

    if go build ./... 2>&1; then
      echo "    Build OK after templ regenerate"
    else
      echo "    Still failing — regenerating sqlc files..."
      if command -v sqlc &>/dev/null; then
        # sqlc generate doesn't delete outputs for removed queries, so orphaned
        # *.sql.go files can linger and break the build. Wipe all sqlc outputs
        # before regenerating to ensure a clean slate.
        find internal/db -maxdepth 1 -type f -name '*.go' -print0 \
          | xargs -0 grep -l '^// Code generated by sqlc' 2>/dev/null \
          | xargs rm -f 2>/dev/null
        if sqlc generate 2>&1; then
          echo "    sqlc regenerated"
          if go build ./... 2>&1; then
            echo "    Build OK after regeneration"
          else
            echo "WARN: go build still failing after templ + sqlc regenerate"
          fi
        else
          echo "WARN: sqlc generate failed"
        fi
      else
        echo "WARN: sqlc not available, build may be broken"
      fi
    fi
  else
    echo "    Build OK"
  fi

  # Inject env vars if CLAUDE_ENV_FILE is available
  if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
    # DATABASE_URL
    if [ -z "${DATABASE_URL:-}" ]; then
      echo 'DATABASE_URL=postgres://breadbox:breadbox@localhost:5432/breadbox?sslmode=disable' >> "$CLAUDE_ENV_FILE"
      echo "    Set DATABASE_URL"
    fi

    # ENCRYPTION_KEY — precedence: .local.env (source of truth) > running process > cache.
    # .local.env is the canonical store (loaded by godotenv at server start), so we read
    # it first to avoid the regression where a stale cache silently shadows a rotated key.
    # Cache survives between sessions so a fresh worktree with no `.local.env` and no
    # `breadbox serve` running still gets the key without a manual `ps eww` round-trip.
    if [ -z "${ENCRYPTION_KEY:-}" ]; then
      KEY_CACHE="$HOME/.local/share/breadbox/dev-encryption-key"
      EK=""
      EK_SRC=""

      # 1) .local.env in the worktree (copied in by .worktreeinclude)
      LOCAL_ENV="$PROJECT_DIR/.local.env"
      if [ -f "$LOCAL_ENV" ]; then
        CAND="$(grep -E '^ENCRYPTION_KEY=' "$LOCAL_ENV" 2>/dev/null | tail -1 | cut -d= -f2- | tr -d '"'"'"'\r\n' || true)"
        if [[ "$CAND" =~ ^[0-9a-fA-F]{64}$ ]]; then
          EK="$CAND"
          EK_SRC=".local.env"
        fi
      fi

      # 2) running `breadbox serve` process
      if [ -z "$EK" ]; then
        RUNNING_PID="$(pgrep -f 'breadbox serve' | head -1 || true)"
        if [ -n "$RUNNING_PID" ]; then
          CAND="$(ps eww -p "$RUNNING_PID" 2>/dev/null | tr ' ' '\n' | grep '^ENCRYPTION_KEY=' | cut -d= -f2- | tr -d '\r' || true)"
          if [[ "$CAND" =~ ^[0-9a-fA-F]{64}$ ]]; then
            EK="$CAND"
            EK_SRC="running process"
            # Persist to cache for future sessions (atomic write, subshell-scoped umask)
            mkdir -p "$(dirname "$KEY_CACHE")"
            (
              umask 077
              printf '%s\n' "$EK" > "$KEY_CACHE.tmp" && mv -f "$KEY_CACHE.tmp" "$KEY_CACHE"
            ) || echo "WARN: failed to persist ENCRYPTION_KEY cache at $KEY_CACHE"
          fi
        fi
      fi

      # 3) on-disk cache (fallback when neither above worked)
      if [ -z "$EK" ] && [ -f "$KEY_CACHE" ]; then
        CAND="$(head -1 "$KEY_CACHE" 2>/dev/null | tr -d '\r\n' || true)"
        if [[ "$CAND" =~ ^[0-9a-fA-F]{64}$ ]]; then
          EK="$CAND"
          EK_SRC="cache ($KEY_CACHE)"
        else
          echo "WARN: $KEY_CACHE exists but doesn't contain a 64-char hex key; ignoring"
        fi
      fi

      if [ -n "$EK" ]; then
        echo "ENCRYPTION_KEY=$EK" >> "$CLAUDE_ENV_FILE"
        echo "    Set ENCRYPTION_KEY from $EK_SRC"
      else
        echo "WARN: ENCRYPTION_KEY not found. Start \`breadbox serve\` once (so the hook can"
        echo "      cache the key), or paste just the 64 hex chars (no \`ENCRYPTION_KEY=\`"
        echo "      prefix, no quotes) into $KEY_CACHE"
      fi
    fi

    # PORT — reserve a deterministic port via the shared dev-server registry
    # (scripts/dev-lib.sh, sourced above). One registry — keyed by worktree,
    # reaped automatically — replaces the old ad-hoc .claude/port-locks files.
    # `make dev`, `make dev-bg`, and the validation scripts all resolve the
    # same port, so nothing has to fish for a free one.
    if [ -z "${PORT:-}" ] && command -v bb_claim_port >/dev/null 2>&1; then
      # Key the reservation by the git worktree root (canonicalized) so it
      # matches what dev-server / dev-port / session-end resolve — keying by a
      # raw CLAUDE_PROJECT_DIR that differs (trailing slash, /private symlink)
      # would orphan the reservation.
      ASSIGNED_PORT="$(bb_claim_port "$(bb_worktree_root "$PROJECT_DIR")" RESERVED || true)"
      if [ -n "$ASSIGNED_PORT" ]; then
        # Export both: PORT is consumed by the Makefile, SERVER_PORT by the
        # binary — so `make dev` and a direct `breadbox serve` both land here.
        echo "PORT=$ASSIGNED_PORT" >> "$CLAUDE_ENV_FILE"
        echo "SERVER_PORT=$ASSIGNED_PORT" >> "$CLAUDE_ENV_FILE"
        echo "    Reserved port $ASSIGNED_PORT (PORT + SERVER_PORT exported)"
      else
        echo "WARN: no available port in 8081-8099 (try: make dev-reap)"
      fi
    fi

    # BREADBOX_AGENT_TRANSCRIPT_DIR — share one transcript dir across every
    # worktree-resident `breadbox serve`. Without this each worktree writes
    # transcripts to its own ./transcripts/agents/ (cwd-relative default),
    # so a run launched from worktree A is unreadable when the admin UI is
    # served by worktree B's process. The path lives outside any worktree
    # so it survives `claude --rm` and switching branches.
    if [ -z "${BREADBOX_AGENT_TRANSCRIPT_DIR:-}" ]; then
      TX_DIR="$HOME/.local/share/breadbox/transcripts/agents"
      mkdir -p "$TX_DIR" 2>/dev/null || true
      echo "BREADBOX_AGENT_TRANSCRIPT_DIR=$TX_DIR" >> "$CLAUDE_ENV_FILE"
      echo "    Set BREADBOX_AGENT_TRANSCRIPT_DIR ($TX_DIR)"
    fi

    # breadbox-agent binary — auto-discovery checks ./bin, then
    # ~/.breadbox/agent-bin, then $PATH. Verify at least one slot resolves
    # and hint if not so the first `breadbox agent test` doesn't fail
    # with a cryptic "binary not found".
    if [ ! -x "$PROJECT_DIR/bin/breadbox-agent" ] \
       && [ ! -x "$HOME/.breadbox/agent-bin/breadbox-agent" ] \
       && ! command -v breadbox-agent &>/dev/null; then
      echo "    Note: breadbox-agent sidecar not found in any discovery slot."
      echo "          Run \`make agent-sidecar-install-user\` in the main repo to"
      echo "          install it once at ~/.breadbox/agent-bin/breadbox-agent —"
      echo "          every worktree will pick it up automatically thereafter."
    fi
  fi

  echo "==> Worktree setup complete."
  exit 0
fi

# --- Remote (web) sessions below ---

SQLC_VERSION="1.30.0"

# --- Go modules ---
echo "==> Downloading Go modules..."
if ! go mod download 2>&1; then
  echo "WARN: go mod download failed, continuing anyway"
fi

# --- sqlc ---
echo "==> Installing sqlc ${SQLC_VERSION}..."
if ! command -v sqlc &>/dev/null || [ "$(sqlc version 2>/dev/null)" != "v${SQLC_VERSION}" ]; then
  curl -sL -m 120 "https://github.com/sqlc-dev/sqlc/releases/download/v${SQLC_VERSION}/sqlc_${SQLC_VERSION}_linux_amd64.tar.gz" \
    | tar xz -C /usr/local/bin sqlc \
    && echo "    sqlc $(sqlc version) installed" \
    || echo "WARN: sqlc install failed"
fi

echo "==> Generating sqlc code..."
if ! sqlc generate 2>&1; then
  echo "WARN: sqlc generate failed"
fi

# --- templ ---
echo "==> Installing templ and generating components..."
if ! command -v templ &>/dev/null; then
  go install github.com/a-h/templ/cmd/templ@latest \
    && echo "    templ installed" \
    || echo "WARN: templ install failed"
fi
if command -v templ &>/dev/null; then
  if ! templ generate 2>&1; then
    echo "WARN: templ generate failed"
  fi
fi

# --- CSS ---
echo "==> Building CSS (downloads tailwindcss-extra if needed)..."
if ! make css 2>&1; then
  echo "WARN: make css failed"
fi

# --- Graphite CLI (for stacked PRs) ---
# Installed eagerly so /stack is ready without a second round-trip. Skip
# silently if npm isn't available — single-PR work doesn't need gt.
if command -v npm &>/dev/null; then
  echo "==> Installing Graphite CLI..."
  if ! command -v gt &>/dev/null; then
    npm install -g @withgraphite/graphite-cli@stable >/dev/null 2>&1 \
      && echo "    gt installed" \
      || echo "WARN: gt install failed — /stack will not work until fixed"
  else
    echo "    gt already installed"
  fi
  # Initialize repo metadata (idempotent).
  if command -v gt &>/dev/null && [ ! -f .graphite_repo_config ]; then
    gt repo init --trunk main >/dev/null 2>&1 || true
  fi
fi

# --- Postgres + databases ---
# Cloud sessions start with no Postgres data dir state guaranteed. Bring up
# the cluster and ensure both the test DB (used by `make test-integration`)
# and the dev DB (used by `breadbox serve` and `make dev`) exist with the
# `breadbox` role. All steps idempotent.
echo "==> Setting up Postgres + dev/test databases..."
DB_READY=false
if command -v pg_isready &>/dev/null; then
  if ! pg_isready -q 2>/dev/null; then
    pg_ctlcluster 16 main start 2>/dev/null || true
  fi
  sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='breadbox'" 2>/dev/null | grep -q 1 \
    || sudo -u postgres psql -c "CREATE ROLE breadbox WITH LOGIN PASSWORD 'breadbox'" 2>/dev/null
  sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='breadbox'" 2>/dev/null | grep -q 1 \
    || sudo -u postgres createdb -O breadbox breadbox 2>/dev/null
  sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='breadbox_test'" 2>/dev/null | grep -q 1 \
    || sudo -u postgres createdb -O breadbox breadbox_test 2>/dev/null
  echo "    breadbox + breadbox_test databases ready"
  DB_READY=true
else
  echo "WARN: pg_isready not found, skipping DB setup"
fi

# --- ENCRYPTION_KEY (cloud sessions) ---
# Mint once and persist to the same cache path worktrees use, so subsequent
# restarts of the same container reuse the key and any rows encrypted under
# it (Plaid/Teller tokens, Teller PEMs) keep decrypting.
KEY_CACHE="$HOME/.local/share/breadbox/dev-encryption-key"
EK=""
if [ -f "$KEY_CACHE" ]; then
  CAND="$(head -1 "$KEY_CACHE" 2>/dev/null | tr -d '\r\n' || true)"
  if [[ "$CAND" =~ ^[0-9a-fA-F]{64}$ ]]; then
    EK="$CAND"
  fi
fi
if [ -z "$EK" ]; then
  if command -v openssl &>/dev/null; then
    EK="$(openssl rand -hex 32)"
  else
    EK="$(od -A n -t x1 -N 32 /dev/urandom | tr -d ' \n')"
  fi
  mkdir -p "$(dirname "$KEY_CACHE")"
  (
    umask 077
    printf '%s\n' "$EK" > "$KEY_CACHE.tmp" && mv -f "$KEY_CACHE.tmp" "$KEY_CACHE"
  ) || echo "WARN: failed to persist ENCRYPTION_KEY cache at $KEY_CACHE"
  echo "    Minted fresh ENCRYPTION_KEY (cached at $KEY_CACHE)"
else
  echo "    Reusing ENCRYPTION_KEY from $KEY_CACHE"
fi

# --- Env vars for the agent's shell ---
# Mirrors what the worktree branch above writes: a fresh `breadbox serve`,
# `bin/breadbox migrate`, etc. should pick the right DB + key + port with
# no extra exports.
DB_URL='postgres://breadbox:breadbox@localhost:5432/breadbox?sslmode=disable'
BB_ADMIN_USER='admin@example.com'
BB_ADMIN_PASS='password'
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
  {
    [ -z "${DATABASE_URL:-}" ]   && echo "DATABASE_URL=$DB_URL"
    [ -z "${ENCRYPTION_KEY:-}" ] && echo "ENCRYPTION_KEY=$EK"
    [ -z "${SERVER_PORT:-}" ]    && echo "SERVER_PORT=8081"
    [ -z "${PORT:-}" ]           && echo "PORT=8081"
    [ -z "${BB_USER:-}" ]        && echo "BB_USER=$BB_ADMIN_USER"
    [ -z "${BB_PASS:-}" ]        && echo "BB_PASS=$BB_ADMIN_PASS"
  } >> "$CLAUDE_ENV_FILE"
  echo "    Wrote DATABASE_URL + ENCRYPTION_KEY + SERVER_PORT + BB_USER/BB_PASS to CLAUDE_ENV_FILE"
fi
# Also export in-process so the migrate / create-admin steps below see them.
export DATABASE_URL="$DB_URL"
export ENCRYPTION_KEY="$EK"

# --- Build the dev binary ---
# Used by the migrate + create-admin steps below, and reusable by the agent
# (`bin/breadbox serve`, `bin/breadbox migrate`, etc.).
echo "==> Building bin/breadbox..."
mkdir -p bin
if go build -o bin/breadbox ./cmd/breadbox 2>&1; then
  echo "    bin/breadbox built"
else
  echo "WARN: go build failed — bin/breadbox not available"
fi

# --- Apply migrations + seed admin ---
# Goose tracks state in the DB so `migrate up` is a no-op on warm starts.
# `create-admin` errors with "already exists" on warm starts — fine, swallow
# it. Credentials are exposed via BB_USER/BB_PASS above so the validate-ui
# skill can log in without rediscovering them.
if [ "$DB_READY" = "true" ] && [ -x bin/breadbox ]; then
  echo "==> Applying migrations to breadbox..."
  if ! bin/breadbox migrate up 2>&1 | tail -3; then
    echo "WARN: migrate up failed"
  fi

  echo "==> Seeding admin user ($BB_ADMIN_USER)..."
  SEED_OUT="$(bin/breadbox create-admin --username "$BB_ADMIN_USER" --password "$BB_ADMIN_PASS" 2>&1 || true)"
  case "$SEED_OUT" in
    *"created successfully"*) echo "    admin created" ;;
    *"already exists"*)       echo "    admin already exists" ;;
    *)                        echo "WARN: create-admin: $SEED_OUT" ;;
  esac
fi

echo "==> Session setup complete."
echo "    Next: \`bin/breadbox serve\` (listens on :\$SERVER_PORT, default 8081)."
echo "    Admin login: $BB_ADMIN_USER / $BB_ADMIN_PASS"
