#!/usr/bin/env bash
# Codex4Less — Codex Desktop provider setup (macOS / Linux)
# Usage:
#   curl -fsSL https://codex4less.com/scripts/install-codex-desktop.sh | bash -s -- --model "gpt-6.1-sol"
#   ./install-codex-desktop.sh --help
#   ./install-codex-desktop.sh --uninstall
# The script never downloads a binary and never touches chat databases.

set -euo pipefail
umask 077

BASE_URL="https://api.codex4less.com/v1"
PROVIDER_ID="Codex4Less"
PROVIDER_NAME="Codex4Less"
MODEL="gpt-6-astra"
API_KEY=""
DRY_RUN=0
UNINSTALL=0
YES=0
DIR="${CODEX_HOME:-$HOME/.codex}"
CONFIG="$DIR/config.toml"
AUTH="$DIR/auth.json"
MARKER="$DIR/.codex4less-install.env"
ALLOWED_MODELS="gpt-6-astra gpt-6.1-sol gpt-5.6-sol claude-opus-5 claude-sonnet-5 claude-sonnet-5-5"

info() { printf '  %s\n' "$*"; }
ok()   { printf '  [OK] %s\n' "$*"; }
warn() { printf '  [!] %s\n' "$*"; }
fail() { printf '  [X] %s\n' "$*" >&2; }

usage() {
  cat <<'EOF'
Codex4Less — Codex Desktop setup

Options:
  --api-key <sk-...>     API key. If omitted, the script asks for it.
  --model <model-id>     Model to select. Default: gpt-6-astra.
  --base-url <url>       Override the API base URL (https only).
  --uninstall            Remove only the Codex4Less provider and restore backups.
  --dry-run              Print the plan without writing files or calling the API.
  --yes                  Do not ask for confirmation.
  --help                 Show this help.

Allowed models:
  gpt-6-astra
  gpt-6.1-sol
  gpt-5.6-sol
  claude-opus-5
  claude-sonnet-5
  claude-sonnet-5-5
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --api-key) API_KEY="${2:-}"; shift 2 ;;
    --model) MODEL="${2:-}"; shift 2 ;;
    --base-url) BASE_URL="${2:-}"; shift 2 ;;
    --uninstall) UNINSTALL=1; shift ;;
    --dry-run) DRY_RUN=1; shift ;;
    --yes|-y) YES=1; shift ;;
    --help|-h) usage; exit 0 ;;
    *) fail "Unknown option: $1"; usage; exit 2 ;;
  esac
done

is_allowed_model() {
  for m in $ALLOWED_MODELS; do
    [ "$m" = "$1" ] && return 0
  done
  return 1
}

if ! is_allowed_model "$MODEL"; then
  fail "Model is not in the Codex4Less catalog allowlist: $MODEL"
  info "Allowed: $ALLOWED_MODELS"
  exit 2
fi

case "$BASE_URL" in
  https://*) ;;
  *) fail "Base URL must use https:// (got: $BASE_URL)"; exit 2 ;;
esac

hash_key() {
  if command -v sha256sum >/dev/null 2>&1; then
    printf '%s' "$1" | sha256sum | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
  else
    printf ''
  fi
}

read_key() {
  if [ -z "$API_KEY" ] && [ -n "${CODEX4LESS_API_KEY:-}" ]; then
    API_KEY="$CODEX4LESS_API_KEY"
  fi
  if [ -n "$API_KEY" ]; then
    return 0
  fi
  printf '\n  Paste your Codex4Less API key (sk-...):\n'
  printf '  API_KEY: '
  if [ -t 0 ]; then
    if ! read -r -s API_KEY; then API_KEY=""; fi
    printf '\n'
  elif [ -r /dev/tty ]; then
    if ! read -r -s API_KEY < /dev/tty; then API_KEY=""; fi
    printf '\n'
  fi
  API_KEY="$(printf '%s' "${API_KEY:-}" | tr -d '\r' | sed -e 's/^[[:space:]"]*//' -e 's/[[:space:]"]*$//')"
  if [ -z "$API_KEY" ]; then
    fail "No API key provided. Re-run with --api-key or from an interactive terminal."
    exit 1
  fi
}

validate_key() {
  if ! printf '%s' "$API_KEY" | grep -q '^sk-[A-Za-z0-9]'; then
    fail "Invalid API key format: it must start with sk- and not be quoted."
    exit 1
  fi
  info "Validating the key against $BASE_URL/models ..."
  code="$(curl -sS --max-time 20 -o /dev/null -w '%{http_code}' "$BASE_URL/models" -H "Authorization: Bearer $API_KEY" 2>/dev/null || printf '000')"
  case "$code" in
    200) ok "API key is valid." ;;
    401) fail "Invalid API key (401). Check Dashboard > API Keys."; exit 1 ;;
    402) fail "No usable balance for this key (402). Top up in the dashboard."; exit 1 ;;
    403) fail "Account is not allowed to use the API (403). Check verification/account status."; exit 1 ;;
    429) fail "Rate limited while validating (429). Try again shortly."; exit 1 ;;
    000) fail "Could not reach Codex4Less. Check your network."; exit 1 ;;
    *) fail "Unexpected HTTP $code while validating the key."; exit 1 ;;
  esac
}

backup_file() {
  local file="$1"
  if [ -f "$file" ]; then
    local backup="${file}.bak-$(date +%Y%m%d-%H%M%S)-$$"
    cp -p "$file" "$backup"
    printf '%s' "$backup"
  fi
}

root_value() {
  local key="$1" file="$2"
  awk -v k="$key" '
    BEGIN { root=1 }
    /^[[:space:]]*\[/ { root=0 }
    root && $0 ~ "^[[:space:]]*" k "[[:space:]]*=" {
      line=$0
      sub(/^[^=]*=[[:space:]]*/, "", line)
      gsub(/^"|"$/, "", line)
      value=line
    }
    END { print value }
  ' "$file"
}

strip_provider_and_root() {
  local file="$1" out="$2"
  awk -v pid="$PROVIDER_ID" '
    function is_table(line) { return line ~ /^[[:space:]]*\[/ }
    BEGIN { root=1; skip=0 }
    {
      if (is_table($0)) {
        if (skip) skip=0
        root=0
      }
      if (skip) next
      if ($0 ~ /^[[:space:]]*# Codex4Less gateway/) next
      if ($0 ~ "^[[:space:]]*\\[model_providers\\." pid "\\][[:space:]]*$") { skip=1; next }
      if (root && $0 ~ /^[[:space:]]*model_provider[[:space:]]*=/) next
      if (root && $0 ~ /^[[:space:]]*model[[:space:]]*=/) next
      print
    }
  ' "$file" > "$out"
}

do_uninstall() {
  info "Removing the Codex4Less provider from $CONFIG"
  if [ -f "$MARKER" ]; then
    # shellcheck disable=SC1090
    . "$MARKER"
  fi
  mkdir -p "$DIR"
  local prev_model="${PREVIOUS_MODEL:-}"
  local prev_provider="${PREVIOUS_MODEL_PROVIDER:-}"
  if [ -f "$CONFIG" ]; then
    local config_backup
    config_backup="$(backup_file "$CONFIG")"
    [ -n "$config_backup" ] && info "Backup: $config_backup"
    local tmp="$CONFIG.tmp.$$"
    strip_provider_and_root "$CONFIG" "$tmp"
    {
      [ -n "$prev_provider" ] && printf 'model_provider = "%s"\n' "$prev_provider"
      [ -n "$prev_model" ] && printf 'model = "%s"\n' "$prev_model"
      cat "$tmp"
    } > "$CONFIG"
    rm -f "$tmp"
    ok "Codex4Less provider block removed; other settings preserved."
  else
    warn "No config.toml found; nothing to remove."
  fi

  if [ -n "${AUTH_KEY_HASH:-}" ] && [ -f "$AUTH" ]; then
    local current_hash
    current_hash="$(sed -n 's/.*"OPENAI_API_KEY"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$AUTH" | head -n 1)"
    current_hash="$(hash_key "$current_hash")"
    if [ "$current_hash" = "$AUTH_KEY_HASH" ]; then
      if [ "${AUTH_CREATED:-0}" = "1" ]; then
        rm -f "$AUTH"
        ok "auth.json removed (it was created by this installer)."
      elif [ -n "${AUTH_BACKUP:-}" ] && [ -f "${AUTH_BACKUP}" ]; then
        cp -p "$AUTH_BACKUP" "$AUTH"
        ok "auth.json restored from backup."
      else
        warn "A matching auth backup was not found; auth.json left in place."
      fi
    else
      warn "auth.json contains a different key now; leaving it untouched."
    fi
  fi
  rm -f "$MARKER"
  ok "Uninstall complete. Chat history and other providers were not touched."
}

if [ "$UNINSTALL" = "1" ]; then
  do_uninstall
  exit 0
fi

if [ "$DRY_RUN" = "1" ]; then
  info "Dry run: no files will be written and no API call will be made."
  info "Target directory: $DIR"
  info "Config file: $CONFIG"
  info "Auth file: $AUTH"
  info "Provider: $PROVIDER_ID"
  info "Model: $MODEL"
  info "Base URL: $BASE_URL"
  info "Wire API: responses"
  info "Backup: timestamped .bak file before writing"
  exit 0
fi

read_key
validate_key

mkdir -p "$DIR"
SAVED_PREV_MODEL=""
SAVED_PREV_PROVIDER=""
SAVED_AUTH_BACKUP=""
SAVED_AUTH_CREATED=""
HAS_MARKER=0
if [ -f "$MARKER" ]; then
  # Keep the values from the first install; a reinstall must not overwrite them
  # with the Codex4Less values it wrote last time.
  # shellcheck disable=SC1090
  REQUESTED_MODEL="$MODEL"
  . "$MARKER"
  MODEL="$REQUESTED_MODEL"
  HAS_MARKER=1
  SAVED_PREV_MODEL="${PREVIOUS_MODEL:-}"
  SAVED_PREV_PROVIDER="${PREVIOUS_MODEL_PROVIDER:-}"
  SAVED_AUTH_BACKUP="${AUTH_BACKUP:-}"
  SAVED_AUTH_CREATED="${AUTH_CREATED:-0}"
fi

AUTH_BACKUP=""
AUTH_CREATED=0
if [ -f "$AUTH" ]; then
  current_auth_backup="$(backup_file "$AUTH")"
  [ -n "$current_auth_backup" ] && info "Backup: $current_auth_backup"
  if [ "$HAS_MARKER" = "1" ]; then
    AUTH_BACKUP="$SAVED_AUTH_BACKUP"
    AUTH_CREATED="$SAVED_AUTH_CREATED"
  else
    AUTH_BACKUP="$current_auth_backup"
    AUTH_CREATED=0
  fi
elif [ "$HAS_MARKER" = "1" ]; then
  AUTH_BACKUP="$SAVED_AUTH_BACKUP"
  AUTH_CREATED="$SAVED_AUTH_CREATED"
else
  AUTH_CREATED=1
fi

PREVIOUS_MODEL=""
PREVIOUS_MODEL_PROVIDER=""
if [ -f "$CONFIG" ]; then
  if [ "$HAS_MARKER" = "1" ]; then
    PREVIOUS_MODEL="$SAVED_PREV_MODEL"
    PREVIOUS_MODEL_PROVIDER="$SAVED_PREV_PROVIDER"
  else
    PREVIOUS_MODEL="$(root_value model "$CONFIG")"
    PREVIOUS_MODEL_PROVIDER="$(root_value model_provider "$CONFIG")"
  fi
  config_backup="$(backup_file "$CONFIG")"
  [ -n "$config_backup" ] && info "Backup: $config_backup"
fi

if [ ! -f "$CONFIG" ]; then
  : > "$CONFIG"
fi

TMP="$CONFIG.tmp.$$"
strip_provider_and_root "$CONFIG" "$TMP"
{
  printf 'model = "%s"\n' "$MODEL"
  printf 'model_provider = "%s"\n' "$PROVIDER_ID"
  printf '\n# Codex4Less gateway (keys: Dashboard > API Keys)\n'
  printf '[model_providers.%s]\n' "$PROVIDER_ID"
  printf 'name = "%s"\n' "$PROVIDER_NAME"
  printf 'base_url = "%s"\n' "$BASE_URL"
  printf 'wire_api = "responses"\n'
  printf 'requires_openai_auth = true\n'
  printf '\n'
  cat "$TMP"
} > "$CONFIG"
rm -f "$TMP"
ok "config.toml updated: $CONFIG"

printf '{\n  "auth_mode": "apikey",\n  "OPENAI_API_KEY": "%s"\n}\n' "$API_KEY" > "$AUTH"
ok "auth.json updated: $AUTH"

KEY_HASH="$(hash_key "$API_KEY")"
{
  printf 'PREVIOUS_MODEL=%q\n' "$PREVIOUS_MODEL"
  printf 'PREVIOUS_MODEL_PROVIDER=%q\n' "$PREVIOUS_MODEL_PROVIDER"
  printf 'AUTH_BACKUP=%q\n' "$AUTH_BACKUP"
  printf 'AUTH_CREATED=%q\n' "$AUTH_CREATED"
  printf 'AUTH_KEY_HASH=%q\n' "$KEY_HASH"
  printf 'MODEL=%q\n' "$MODEL"
  printf 'PROVIDER=%q\n' "$PROVIDER_ID"
} > "$MARKER"

echo ""
ok "Codex Desktop is configured for Codex4Less."
info "Model: $MODEL"
info "Quit and reopen Codex so it reads the new config."
info "To undo: run this script with --uninstall."
info "Support: info@codex4less.com"
