#!/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

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)
  --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. Detects and configures IDEs (OpenClaw, Cline, Continue.dev, Cursor)
  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 for versioned IDs like claude-haiku-4-5-20251001)
model_meta = {
    "claude-opus-4": {"name": "Claude Opus (OCP)", "reasoning": True, "maxTokens": 16384},
    "claude-sonnet-4": {"name": "Claude Sonnet (OCP)", "reasoning": True, "maxTokens": 16384},
    "claude-haiku-4": {"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 (prefix match)
alias_prefixes = {
    "claude-opus-4": "Claude Opus",
    "claude-sonnet-4": "Claude Sonnet",
    "claude-haiku-4": "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 sonnet for daily use)
    primary_model = provider_name + "/claude-sonnet-4-6" if "claude-sonnet-4-6" in model_ids else 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")
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

  # Detect Cline (VS Code extension)
  if [[ -d "$HOME/.vscode/extensions" ]] && ls "$HOME/.vscode/extensions/" 2>/dev/null | grep -qi cline; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Cline: Settings → OPENAI_BASE_URL = $base_url/v1"
  fi

  # Detect Continue.dev
  if [[ -f "$HOME/.continue/config.json" ]]; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Continue.dev: ~/.continue/config.json → apiBase: \"$base_url/v1\""
  fi

  # Detect Cursor
  if command -v cursor &>/dev/null || [[ -d "$HOME/.cursor" ]]; then
    if ! $other_ides_shown; then
      echo "  Other IDEs detected:"
      other_ides_shown=true
    fi
    echo "    • Cursor: Settings → OpenAI Base URL = $base_url/v1"
  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'}"; shift 2 ;;
      --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"
  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 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
      echo
      if [[ -z "$key" ]]; then
        echo ""
        echo "  ✗ No key provided — cannot connect to an auth-required remote without a 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")
  else
    # Always write both on macOS (default shell is zsh but some tools source bashrc)
    [[ -f "$HOME/.bashrc" || "${SHELL:-}" == */bash ]] && rc_files+=("$HOME/.bashrc")
    [[ -f "$HOME/.zshrc" || "${SHELL:-}" == */zsh ]] && rc_files+=("$HOME/.zshrc")
    # If neither exists, create for 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"
  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"
    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"
  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:"
  echo "    source $rc_file"
}

main "$@"
