#!/usr/bin/env bash
# ocp-connect — Lightweight client script to connect to a remote OCP instance
# No dependencies beyond curl and python3 (available on most Linux/Mac systems)
#
# Install:
#   curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect -o ocp-connect
#   chmod +x ocp-connect
#
# Or run directly:
#   curl -fsSL https://raw.githubusercontent.com/dtzp555-max/ocp/main/ocp-connect | bash -s -- <host-ip> --key <key>
#
set -euo pipefail

OCP_CONNECT_VERSION="1.3.0"

show_version() {
  echo "ocp-connect $OCP_CONNECT_VERSION"
}

show_help() {
  cat <<'EOF'
ocp-connect — Connect this machine to a remote OCP (Open Claude Proxy)

Configures OPENAI_BASE_URL and OPENAI_API_KEY in your shell rc file
so tools like Claude Code, Cline, Aider, etc. point to the remote OCP.

Usage:
  ocp-connect <host-ip> [options]

Options:
  --port PORT      Port OCP listens on (default: 3456)
  --key  API_KEY   API key (prompted interactively if remote requires auth)
  --version        Print version and exit
  --help, -h       Show this help

Examples:
  ocp-connect 192.168.1.10
  ocp-connect 192.168.1.10 --port 8080
  ocp-connect 192.168.1.10 --key ocp_abc123

What it does:
  1. Tests connectivity to the remote OCP
  2. Verifies your API key (if auth is enabled)
  3. Writes OPENAI_BASE_URL and OPENAI_API_KEY to ~/.bashrc or ~/.zshrc
  4. Sets system-level env vars (launchctl on macOS, systemd on Linux)
  5. Configures OpenClaw automatically; prints setup hints for other IDEs
     (Cline, Continue.dev, Cursor — manual configuration required)
  6. Runs a smoke test to confirm everything works

After running, reload your shell:  source ~/.bashrc  (or ~/.zshrc)
EOF
}

configure_ides() {
  local base_url="$1" key="$2" models_out="$3"

  # --- OpenClaw ---
  local oc_config="$HOME/.openclaw/openclaw.json"
  local oc_found=false
  if command -v openclaw &>/dev/null || [[ -f "$oc_config" ]]; then
    oc_found=true
  fi

  if $oc_found; then
    echo "  IDE Configuration"
    echo "  ─────────────────────────────────────"
    echo "  Detected: OpenClaw ($oc_config)"
    echo ""
    printf "  Configure OpenClaw to use this OCP? [Y/n] "
    local oc_answer
    { read -r oc_answer </dev/tty; } 2>/dev/null || oc_answer="y"
    oc_answer="${oc_answer:-y}"

    if [[ "$oc_answer" =~ ^[Yy]$ ]]; then
      # Ask for provider name
      printf "  Provider name (models show as <name>/model-id) [ocp]: "
      local provider_name
      { read -r provider_name </dev/tty; } 2>/dev/null || provider_name=""
      provider_name="${provider_name:-ocp}"
      # Sanitize: only allow alphanumeric, dash, underscore
      provider_name=$(echo "$provider_name" | tr -cd 'a-zA-Z0-9_-')
      [[ -z "$provider_name" ]] && provider_name="ocp"

      # Ask for priority
      echo ""
      echo "  How should OCP models be configured?"
      echo "    1) Primary — use OCP by default, keep existing models as backup"
      echo "    2) Backup  — keep current primary, add OCP as additional option"
      echo ""
      printf "  Choice [1]: "
      local priority_choice
      { read -r priority_choice </dev/tty; } 2>/dev/null || priority_choice="1"
      priority_choice="${priority_choice:-1}"

      echo ""
      echo "  Writing OpenClaw config..."

      # Use python3 to safely manipulate JSON
      local py_ok=0
      python3 - "$oc_config" "$base_url" "$key" "$provider_name" "$priority_choice" "$models_out" <<'PYEOF' && py_ok=1
import sys, json, os

config_path = sys.argv[1]
base_url = sys.argv[2]
api_key = sys.argv[3]
provider_name = sys.argv[4]
priority = sys.argv[5]  # "1" = primary, "2" = backup
models_json_str = sys.argv[6]

# Parse models from OCP /v1/models response
try:
    models_data = json.loads(models_json_str)
    model_ids = [m["id"] for m in models_data.get("data", [])]
except:
    model_ids = ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4"]

# Build provider entry
provider = {
    "baseUrl": base_url + "/v1",
    "api": "openai-completions",
    "authHeader": bool(api_key),
    "models": []
}

# Model metadata mapping. Prefix match on the model FAMILY (claude-opus / -sonnet /
# -haiku), not a pinned version. A version-pinned prefix like "claude-sonnet-4"
# silently misses "claude-sonnet-5" and falls through to the non-reasoning /
# 8k-output default (PR #152 review) — every future Sonnet/Opus/Haiku bump would
# re-trip it. Family prefixes classify any versioned ID correctly with no per-model
# edit. (ADR 0003: models.json is the SPOT for model existence; /v1/models does not
# expose reasoning/maxTokens, so family classification stays here.)
model_meta = {
    "claude-opus": {"name": "Claude Opus (OCP)", "reasoning": True, "maxTokens": 16384},
    "claude-sonnet": {"name": "Claude Sonnet (OCP)", "reasoning": True, "maxTokens": 16384},
    "claude-haiku": {"name": "Claude Haiku (OCP)", "reasoning": False, "maxTokens": 8192},
}

def get_model_meta(mid):
    """Match model metadata by prefix."""
    for prefix, meta in sorted(model_meta.items(), key=lambda x: -len(x[0])):
        if mid.startswith(prefix):
            return meta
    return {"name": mid + " (OCP)", "reasoning": False, "maxTokens": 8192}

for mid in model_ids:
    meta = get_model_meta(mid)
    provider["models"].append({
        "id": mid,
        "name": meta["name"],
        "reasoning": meta["reasoning"],
        "input": ["text"],
        "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
        "contextWindow": 200000,
        "maxTokens": meta["maxTokens"]
    })

# Load or create config
if os.path.exists(config_path):
    with open(config_path, "r") as f:
        config = json.load(f)
else:
    os.makedirs(os.path.dirname(config_path), exist_ok=True)
    config = {}

# Ensure models.providers exists
config.setdefault("models", {})
config["models"].setdefault("mode", "merge")
config["models"].setdefault("providers", {})

# Remove any previous OCP provider with the same name
config["models"]["providers"][provider_name] = provider

# Set up auth profile if key is provided
if api_key:
    config.setdefault("auth", {})
    config["auth"].setdefault("profiles", {})
    config["auth"]["profiles"][provider_name + ":default"] = {
        "provider": provider_name,
        "mode": "api_key"
    }

# Configure agent defaults — add model aliases
config.setdefault("agents", {})
config["agents"].setdefault("defaults", {})
config["agents"]["defaults"].setdefault("models", {})

# Build alias map (family prefix match — version-agnostic, see model_meta note)
alias_prefixes = {
    "claude-opus": "Claude Opus",
    "claude-sonnet": "Claude Sonnet",
    "claude-haiku": "Claude Haiku",
}

for mid in model_ids:
    full_id = provider_name + "/" + mid
    alias = mid  # fallback
    for prefix, name in sorted(alias_prefixes.items(), key=lambda x: -len(x[0])):
        if mid.startswith(prefix):
            alias = name
            break
    config["agents"]["defaults"]["models"][full_id] = {"alias": alias}

# Handle primary/backup
if priority == "1":
    # OCP as primary — pick the best model (prefer the latest Sonnet for daily use,
    # tracking the `sonnet` alias default in models.json; fall back across versions).
    _sonnet_pref = ["claude-sonnet-5", "claude-sonnet-4-6"]
    primary_model = next(
        (provider_name + "/" + m for m in _sonnet_pref if m in model_ids),
        provider_name + "/" + model_ids[0],
    )
    config["agents"]["defaults"].setdefault("model", {})
    config["agents"]["defaults"]["model"]["primary"] = primary_model
    # Keep existing fallbacks
    config["agents"]["defaults"]["model"].setdefault("fallbacks", [])

# If backup (priority == "2"), don't change the primary — just add models to the list

# Update agent list entries that use old provider name patterns
# (only update if agents.list exists and has entries using old OCP-like providers)
if "list" in config.get("agents", {}):
    for agent in config["agents"]["list"]:
        agent_model = agent.get("model", {})
        if priority == "1" and agent_model.get("primary", "").startswith("claude-local/"):
            # Migrate from claude-local to new provider
            old_model_id = agent_model["primary"].split("/", 1)[1]
            if old_model_id in model_ids:
                agent_model["primary"] = provider_name + "/" + old_model_id
        # Also update subagents if they use claude-local
        sub = agent.get("subagents", config["agents"]["defaults"].get("subagents", {}))
        # Don't modify subagents in agent entries — they inherit from defaults

# Update defaults subagents model if using claude-local
if priority == "1":
    sub = config["agents"]["defaults"].get("subagents", {})
    if sub.get("model", "").startswith("claude-local/"):
        old_id = sub["model"].split("/", 1)[1]
        if old_id in model_ids:
            sub["model"] = provider_name + "/" + old_id

with open(config_path, "w") as f:
    json.dump(config, f, indent=2, ensure_ascii=False)
    f.write("\n")

# === B1 fix: seed per-agent auth-profiles.json for OpenClaw multi-agent setups ===
# OpenClaw's per-agent auth loader reads <agentDir>/auth-profiles.json (NOT the
# root openclaw.json's auth.profiles section). Without a real key in each agent's
# agentDir, OpenClaw rejects the lane with "No API key found for provider X".
# See https://github.com/dtzp555-max/ocp/issues/12 for the full investigation.
_seeded = []
_failed = []
_skipped_anonymous = []
_profile_key = provider_name + ":default"
# OpenClaw stores per-agent auth in <openclaw_root>/agents/<id>/agent/ when
# the agent has no explicit `agentDir` field — derive that path so the
# default `main` agent (which never sets agentDir) is also seeded.
_openclaw_root = os.path.dirname(config_path)
for _agent in config.get("agents", {}).get("list", []):
    _agent_dir = _agent.get("agentDir")
    if not _agent_dir:
        _agent_id = _agent.get("id")
        if not _agent_id:
            continue
        _agent_dir = os.path.join(_openclaw_root, "agents", _agent_id, "agent")
    if not api_key:
        # Anonymous mode is incompatible with OpenClaw per-agent auth (empty key
        # is dropped by OpenClaw's pi-auth-credentials). Per OCP issue #12 §14
        # decision: take Path C (require --key for OpenClaw multi-agent setups).
        _skipped_anonymous.append(_agent_dir)
        continue
    _profiles_path = os.path.join(_agent_dir, "auth-profiles.json")
    try:
        os.makedirs(_agent_dir, exist_ok=True)
    except OSError as _e:
        _failed.append((_profiles_path, "mkdir: " + str(_e)))
        continue
    if os.path.exists(_profiles_path):
        try:
            with open(_profiles_path) as _f:
                _ap = json.load(_f)
        except json.JSONDecodeError:
            # Corrupted JSON — back up and rebuild from scratch.
            _bak = _profiles_path + ".bak"
            try:
                os.rename(_profiles_path, _bak)
                print(f"    ⚠ {_profiles_path} was corrupt; backed up to {_bak}")
            except OSError:
                pass
            _ap = {"version": 1, "profiles": {}}
        except OSError as _e:
            # Read failure (permissions, disk) — skip to AVOID clobbering
            # the user's existing profile (which may hold other providers' keys).
            _failed.append((_profiles_path, "read: " + str(_e)))
            continue
    else:
        _ap = {"version": 1, "profiles": {}}
    _ap.setdefault("version", 1)
    _ap.setdefault("profiles", {})
    _ap["profiles"][_profile_key] = {
        "type": "api_key",
        "provider": provider_name,
        "key": api_key
    }
    try:
        with open(_profiles_path, "w") as _f:
            json.dump(_ap, _f, indent=2, ensure_ascii=False)
            _f.write("\n")
        os.chmod(_profiles_path, 0o600)
        _seeded.append(_profiles_path)
    except OSError as _e:
        _failed.append((_profiles_path, "write: " + str(_e)))

# Report — all three sections are independent (mixed scenarios are surfaced).
if _seeded:
    print(f"    ✓ Per-agent auth profile seeded ({len(_seeded)}):")
    for _p in _seeded:
        print(f"      • {_p}")
if _failed:
    print(f"    ⚠ Per-agent auth profile FAILED ({len(_failed)}):")
    for _p, _err in _failed:
        print(f"      • {_p}: {_err}")
    print("      OpenClaw will report \"No API key found\" for the failed agents.")
    print("      Fix the underlying error (permissions / disk) and re-run ocp-connect.")
if _skipped_anonymous:
    print(f"    ⚠ OpenClaw multi-agent mode detected ({len(_skipped_anonymous)} agents).")
    print("      Anonymous mode does not work for OpenClaw per-agent auth.")
    print("      Re-run with: ocp-connect <host> --key ocp_xxx")
PYEOF

      if [[ $py_ok -eq 1 ]]; then
        echo "  ✓ OpenClaw configured"
        echo "    Provider: $provider_name"
        echo "    Models:"
        # List models from the already-fetched models_out
        echo "$models_out" | python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
pn='$provider_name'
for m in d.get('data',[]):
    print('      • ' + pn + '/' + m['id'])
" 2>/dev/null
        if [[ "$priority_choice" == "1" ]]; then
          echo "    Priority: PRIMARY (default model)"
        else
          echo "    Priority: BACKUP (available in model selector)"
        fi
        echo ""
        echo "    Restart OpenClaw to apply: openclaw gateway restart"
      else
        echo "  ⚠ Failed to write OpenClaw config. You can configure it manually."
      fi
    else
      echo "  Skipped OpenClaw configuration."
    fi
    echo ""
  fi

  # --- Other IDEs: print manual instructions ---
  local other_ides_shown=false

  # Collect VS Code extension list once (reused by Cline and Continue.dev checks)
  local _vscode_exts=""
  if command -v code &>/dev/null; then
    _vscode_exts=$(code --list-extensions 2>/dev/null || true)
  fi
  if [[ -z "$_vscode_exts" && -d "$HOME/.vscode/extensions" ]]; then
    _vscode_exts=$(ls "$HOME/.vscode/extensions/" 2>/dev/null || true)
  fi

  # Extract model IDs from /v1/models response for use in hints
  local _model_ids
  _model_ids=$(echo "$models_out" | python3 -c "
import sys,json
try:
    d=json.loads(sys.stdin.read())
    print(', '.join(m['id'] for m in d.get('data', [])))
except Exception:
    print('claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5-20251001')
" 2>/dev/null || echo "claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5-20251001")

  # Key display: truncate if longer than 16 chars to avoid screenshot leakage,
  # show explicit "(none)" in anonymous mode so users don't paste blank fields.
  local _key_display
  if [[ -z "$key" ]]; then
    _key_display="(none — anonymous mode; most external IDEs require a non-empty API Key)"
  elif [[ ${#key} -gt 16 ]]; then
    _key_display="${key:0:8}...${key: -4}"
  else
    _key_display="$key"
  fi

  # Detect Cline (VS Code extension saoudrizwan.claude-dev, see issue #12)
  if echo "$_vscode_exts" | grep -qiE 'cline|saoudrizwan\.claude-dev'; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Cline: VSCode → Cline panel → Settings → API Provider = \"OpenAI Compatible\""
    echo "        Base URL: $base_url/v1"
    echo "        API Key:  $_key_display"
    echo "        Model ID: $_model_ids"
  fi

  # Detect Continue.dev (extension ID continue.continue or config file, see issue #12)
  if echo "$_vscode_exts" | grep -qi 'continue\.continue' \
     || [[ -f "$HOME/.continue/config.yaml" ]] \
     || [[ -f "$HOME/.continue/config.json" ]]; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Continue.dev: edit ~/.continue/config.yaml — add under \`models:\` (top-level key):"
    echo "        models:"
    echo "          - name: OCP Sonnet"
    echo "            provider: openai"
    echo "            model: claude-sonnet-4-6"
    echo "            apiBase: $base_url/v1"
    echo "            apiKey: $_key_display"
    echo "        (other model IDs: $_model_ids)"
  fi

  # Detect Cursor (command, ~/.cursor dir, or /Applications/Cursor.app, see issue #12)
  if command -v cursor &>/dev/null \
     || [[ -d "$HOME/.cursor" ]] \
     || [[ -d "/Applications/Cursor.app" ]]; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Cursor: Cmd+Shift+P → 'Cursor Settings' → Models →"
    echo "        OpenAI API Key:           $_key_display"
    echo "        Override OpenAI Base URL: $base_url/v1"
    echo "        Custom OpenAI Models:     $_model_ids"
  fi

  # Detect opencode (https://opencode.ai, SST team CLI, see issue #12)
  if command -v opencode &>/dev/null \
     || [[ -x "$HOME/.opencode/bin/opencode" ]] \
     || [[ -d "$HOME/.local/share/opencode" ]]; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • opencode: not yet auto-configured by ocp-connect (PR follow-up)."
    echo "        Run \`opencode providers login openai\` and provide:"
    echo "          Base URL: $base_url/v1"
    echo "          API Key:  $_key_display"
    echo "        Available model IDs: $_model_ids"
  fi

  if $other_ides_shown; then
    echo ""
  fi
}

main() {
  local host="" port=3456 key=""

  # Parse args
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --port)  port="${2:?'--port requires a value'}"; shift 2 ;;
      --key)   key="${2:?'--key requires a value'}"
               [[ -z "$key" ]] && { echo "Error: --key cannot be empty (omit --key entirely for anonymous mode)"; exit 1; }
               shift 2 ;;
      --version) show_version; exit 0 ;;
      --help|-h) show_help; exit 0 ;;
      -*)      echo "Unknown option: $1"; show_help; exit 1 ;;
      *)       host="$1"; shift ;;
    esac
  done

  if [[ -z "$host" ]]; then
    echo "Error: host IP is required."
    echo ""
    show_help
    exit 1
  fi

  if ! [[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]]; then
    echo "Error: invalid host '$host'"
    exit 1
  fi

  # Check dependencies
  for cmd in curl python3; do
    if ! command -v "$cmd" &>/dev/null; then
      echo "Error: '$cmd' is required but not found."
      exit 1
    fi
  done

  local base_url="http://$host:$port"

  echo "OCP Connect v$OCP_CONNECT_VERSION"
  echo "─────────────────────────────────────"
  echo "  Remote: $base_url"
  echo ""

  # Step 1: Test connectivity via /health
  echo "  Checking connectivity..."
  local health_json
  health_json=$(curl -sf --max-time 10 "$base_url/health" 2>/dev/null) || {
    echo "  ✗ Cannot reach $base_url/health"
    echo "    Make sure OCP is running on $host and bound to 0.0.0.0 (LAN mode)."
    exit 1
  }
  echo "  ✓ Connected"
  echo ""

  # Step 2: Show remote info
  local remote_version auth_mode
  remote_version=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('version','?'))" 2>/dev/null || echo "?")
  auth_mode=$(echo "$health_json" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('authMode','none'))" 2>/dev/null || echo "none")

  echo "  Remote OCP v$remote_version  (auth: $auth_mode)"
  echo ""

  # Step 2.5: auto-discover anonymous key from /health (issue #12 §14 Path A).
  # The server advertises anonymousKey in /health ONLY when the admin has set
  # PROXY_ADVERTISE_ANON_KEY=1 (default off — /health is unauthenticated, so
  # advertising exposes the shared key to any LAN-reachable device; issue #109).
  # Localhost callers always receive it regardless. When the field is absent,
  # ocp-connect falls back to anonymous access / interactive --key (step 3 below).
  if [[ -z "$key" ]]; then
    local anon_key
    anon_key=$(echo "$health_json" | python3 -c "
import sys, json
try:
    d = json.loads(sys.stdin.read())
    k = d.get('anonymousKey')
except Exception:
    k = None
print(k if k else '')
" 2>/dev/null || echo "")
    if [[ -n "$anon_key" ]]; then
      key="$anon_key"
      local _anon_display="$anon_key"
      if [[ ${#anon_key} -gt 16 ]]; then
        _anon_display="${anon_key:0:8}...${anon_key: -4}"
      fi
      echo "  ⓘ Using server-advertised anonymous key: $_anon_display"
      echo "    (set by admin via PROXY_ANONYMOUS_KEY; see issue #12 §14 Path A)"
      echo ""
    fi
  fi

  # Step 3: Determine if key is needed
  if [[ "$auth_mode" != "none" && -z "$key" ]]; then
    # Try anonymous access first (zero-config: server may allow it)
    if curl -sf --max-time 5 "$base_url/v1/models" >/dev/null 2>&1; then
      echo "  Server allows anonymous access — no key needed."
      echo ""
    else
      echo "  Remote requires authentication."
      echo "  Ask the OCP admin to run:  ocp keys add <name>"
      printf "  Enter API key (or press Enter to skip): "
      { read -rs key </dev/tty; } 2>/dev/null || key=""
      echo
      if [[ -z "$key" ]]; then
        echo ""
        echo "  ✗ No key provided — cannot connect to an auth-required remote without a key."
        echo "    Use:  ocp-connect $host --key <key>"
        exit 1
      fi
    fi
  fi

  # Step 4: Test API access via /v1/models
  echo "  Testing API access..."
  local models_out models_ok=0
  if [[ -n "$key" ]]; then
    models_out=$(curl -sf --max-time 10 \
      -H "Authorization: Bearer $key" \
      "$base_url/v1/models" 2>/dev/null) && models_ok=1
  else
    models_out=$(curl -sf --max-time 10 "$base_url/v1/models" 2>/dev/null) && models_ok=1
  fi

  if [[ $models_ok -eq 0 ]]; then
    echo "  ✗ API access failed — key may be invalid or revoked."
    exit 1
  fi
  local model_count
  model_count=$(echo "$models_out" | python3 -c "import sys,json; print(len(json.loads(sys.stdin.read()).get('data',[])))" 2>/dev/null || echo "?")
  echo "  ✓ API accessible ($model_count models available)"
  echo ""

  # Step 5: Detect shell rc files and OS
  local rc_files=()
  local is_mac=false
  [[ "$(uname)" == "Darwin" ]] && is_mac=true

  # Write to all relevant rc files
  if [[ "${SHELL:-}" == */fish ]]; then
    echo "  Note: fish shell detected. Writing to ~/.bashrc — add to fish config manually."
    rc_files+=("$HOME/.bashrc")
  elif $is_mac; then
    # macOS: default shell since Catalina (2019) is zsh.
    # Always write ~/.zshrc (create if absent — zsh tolerates an empty file).
    # Only write ~/.bashrc if it already exists (don't surprise users with new files).
    [[ -f "$HOME/.bashrc" ]] && rc_files+=("$HOME/.bashrc")
    # zshrc: always include on macOS; create the file if it doesn't exist yet
    [[ -f "$HOME/.zshrc" ]] || touch "$HOME/.zshrc"
    rc_files+=("$HOME/.zshrc")
  else
    # Linux / other: write to whichever rc files already exist or match current shell
    [[ -f "$HOME/.bashrc" || "${SHELL:-}" == */bash ]] && rc_files+=("$HOME/.bashrc")
    [[ -f "$HOME/.zshrc" || "${SHELL:-}" == */zsh ]] && rc_files+=("$HOME/.zshrc")
    # If neither exists, fall back to creating one for the current shell
    [[ ${#rc_files[@]} -eq 0 ]] && rc_files+=("$HOME/.${SHELL##*/}rc")
  fi

  # Step 6: Remove any previously written OCP LAN lines (idempotent) from all rc files
  for rc_file in "${rc_files[@]}"; do
    if [[ -f "$rc_file" ]]; then
      local tmp_rc
      tmp_rc=$(mktemp)
      if python3 - "$rc_file" "$tmp_rc" <<'PYEOF'
import sys
src, dst = sys.argv[1], sys.argv[2]
lines = open(src).readlines()
out = []
skip = False
for line in lines:
    s = line.rstrip('\n')
    if s == '# OCP LAN (added by ocp connect)':
        skip = True
        continue
    if skip and (s == '' or s.startswith('export OPENAI_BASE_URL=') or s.startswith('export OPENAI_API_KEY=')):
        continue
    skip = False
    out.append(line)
open(dst, 'w').writelines(out)
PYEOF
      then
        cp "$tmp_rc" "$rc_file"
      fi
      rm -f "$tmp_rc"
    fi
  done

  # Step 7: Append new config to all rc files
  for rc_file in "${rc_files[@]}"; do
    {
      echo ""
      echo "# OCP LAN (added by ocp connect)"
      echo "export OPENAI_BASE_URL='$base_url/v1'"
      if [[ -n "$key" ]]; then
        echo "export OPENAI_API_KEY='$key'"
      fi
    } >> "$rc_file"
    chmod 600 "$rc_file" 2>/dev/null || true
  done

  echo "  Shell config:"
  for rc_file in "${rc_files[@]}"; do
    echo "    ✓ $(basename "$rc_file")"
  done
  echo "    OPENAI_BASE_URL=$base_url/v1"
  if [[ -n "$key" ]]; then
    echo "    OPENAI_API_KEY=${key:0:8}..."
  fi

  # Step 7b: System-level env vars (for IDEs, daemons, GUI apps)
  if $is_mac; then
    # macOS: launchctl setenv makes vars visible to all GUI apps and launchd services
    launchctl setenv OPENAI_BASE_URL "$base_url/v1" 2>/dev/null
    if [[ -n "$key" ]]; then
      launchctl setenv OPENAI_API_KEY "$key" 2>/dev/null
    fi
    echo ""
    echo "  System-level (launchctl):"
    echo "    ✓ OPENAI_BASE_URL set for GUI apps and daemons"
    echo "    Note: launchctl vars reset on reboot. Add to Login Items or re-run ocp-connect."
  else
    # Linux: write to environment.d for systemd user services
    local env_dir="$HOME/.config/environment.d"
    mkdir -p "$env_dir" 2>/dev/null
    {
      echo "OPENAI_BASE_URL=$base_url/v1"
      if [[ -n "$key" ]]; then
        echo "OPENAI_API_KEY=$key"
      fi
    } > "$env_dir/ocp.conf"
    chmod 600 "$env_dir/ocp.conf" 2>/dev/null || true
    echo ""
    echo "  System-level (systemd):"
    echo "    ✓ $env_dir/ocp.conf"
    echo "    Applies to systemd user services after re-login."
  fi
  echo ""

  # Step 7c: Interactive IDE configuration
  configure_ides "$base_url" "$key" "$models_out"

  # Step 8: Quick smoke test
  echo "  Running smoke test..."
  # Pick the first available model from /v1/models
  local smoke_model
  smoke_model=$(echo "$models_out" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['data'][0]['id'])" 2>/dev/null || echo "claude-haiku-4-5-20251001")
  local chat_payload="{\"model\":\"$smoke_model\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply with OK only.\"}],\"max_tokens\":10}"
  local chat_out chat_ok=0
  if [[ -n "$key" ]]; then
    chat_out=$(curl -sf --max-time 30 \
      -H "Authorization: Bearer $key" \
      -H "Content-Type: application/json" \
      -d "$chat_payload" \
      "$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
  else
    chat_out=$(curl -sf --max-time 30 \
      -H "Content-Type: application/json" \
      -d "$chat_payload" \
      "$base_url/v1/chat/completions" 2>/dev/null) && chat_ok=1
  fi

  if [[ $chat_ok -eq 1 ]]; then
    local reply
    reply=$(echo "$chat_out" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['choices'][0]['message']['content'].strip())" 2>/dev/null || echo "(response received)")
    echo "  ✓ Smoke test passed: $reply"
    echo "    Note: smoke test only verifies OCP is reachable and the key is valid."
    echo "    It does not verify your IDE/agent end-to-end. To verify OpenClaw works,"
    echo "    restart it (\`openclaw gateway restart\`) and send a test message to your bot."
  else
    echo "  ⚠ Smoke test failed (proxy is reachable but chat completion did not succeed)."
    echo "    The env vars have still been written. Check the remote OCP logs."
  fi

  echo ""
  echo "  Done. Reload your shell to apply:"
  for rc_file in "${rc_files[@]}"; do
    echo "    source $rc_file"
  done
}

main "$@"
