#!/usr/bin/env bash
# Codex4Less — Codex CLI provider setup (macOS / Linux)
# Requires an existing Codex CLI install. The script does not download the CLI.
# Usage:
#   curl -fsSL https://codex4less.com/scripts/install-codex-cli.sh | bash -s -- --model "gpt-6.1-sol"
#   ./install-codex-cli.sh --help
#   ./install-codex-cli.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 CLI 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 CLI is configured for Codex4Less."
info "Model: $MODEL"
if ! command -v codex >/dev/null 2>&1; then
  warn "The codex command was not found in PATH. Install Codex CLI from the official source, then restart your shell."
fi
info "Restart the codex command so it reads the new config."
info "To undo: run this script with --uninstall."
info "Support: info@codex4less.com"
