#!/bin/bash

# ============================================================
# CubeAI macOS 更换密钥
#
# 只做四件事：
# 1. 更新 CubeAI 保存的密钥；
# 2. 更新 Claude Code 用户配置中的密钥；
# 3. 更新现有 CubeAI 启动脚本中的密钥；
# 4. 启动已经安装好的 Claude Code。
#
# 不安装或修复 Claude Code，不修改模型，不清理项目配置，
# 不创建隔离配置，不修改 PATH。
# ============================================================

# 兼容 macOS 自带 Bash 3.2。
set -u
set -o pipefail

CUBEAI_BASE_URL="https://icooku.com"

USER_HOME="${HOME}"
CUBEAI_DIR="${USER_HOME}/Library/Application Support/CubeAI"
CUBEAI_CONFIG_FILE="${CUBEAI_DIR}/config.json"
CUBEAI_BIN_DIR="${CUBEAI_DIR}/bin"
CUBEAI_MANAGED_CLAUDE="${CUBEAI_BIN_DIR}/claude"
CUBEAI_OLD_LAUNCHER="${CUBEAI_DIR}/launch-claude.command"

CLAUDE_DIR="${USER_HOME}/.claude"
CLAUDE_SETTINGS_FILE="${CLAUDE_DIR}/settings.json"

LOCAL_BIN_DIR="${USER_HOME}/.local/bin"
CLAUDE_SHIM="${LOCAL_BIN_DIR}/claude"

OLD_TERMINAL_WINDOW_ID=""


# ============================================================
# 极简界面
# ============================================================

clear_screen() {
  /usr/bin/clear 2>/dev/null || true
}

draw_progress() {
  percent="$1"
  suffix="${2:-}"

  if [ "$percent" -lt 0 ]; then
    percent=0
  elif [ "$percent" -gt 100 ]; then
    percent=100
  fi

  width=34
  filled=$((percent * width / 100))
  empty=$((width - filled))
  bar=""
  index=0

  while [ "$index" -lt "$filled" ]; do
    bar="${bar}█"
    index=$((index + 1))
  done

  index=0
  while [ "$index" -lt "$empty" ]; do
    bar="${bar}░"
    index=$((index + 1))
  done

  if [ -n "$suffix" ]; then
    printf '\r[%s] %3d%%  %s' \
      "$bar" "$percent" "$suffix"
  else
    printf '\r[%s] %3d%%' \
      "$bar" "$percent"
  fi
}

capture_current_terminal_window() {
  if [ "${TERM_PROGRAM:-}" = "Apple_Terminal" ]; then
    OLD_TERMINAL_WINDOW_ID="$(
      /usr/bin/osascript \
        -e 'tell application "Terminal" to id of front window' \
        2>/dev/null || true
    )"
  fi
}

normalize_token() {
  raw_value="$1"

  token="$(
    printf '%s' "$raw_value" |
      /usr/bin/sed \
        -e 's/^[[:space:]]*//' \
        -e 's/[[:space:]]*$//' \
        -e 's/^[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]*//'
  )"

  case "$token" in
    \"*\")
      token="${token#\"}"
      token="${token%\"}"
      ;;
    \'*\')
      token="${token#\'}"
      token="${token%\'}"
      ;;
  esac

  token="$(
    printf '%s' "$token" |
      /usr/bin/tr -d '[:space:]'
  )"

  printf '%s' "$token"
}

read_new_token() {
  while true; do
    clear_screen
    printf '%s' "请粘贴新密钥并按回车："
    IFS= read -r raw_token || raw_token=""

    NEW_TOKEN="$(normalize_token "$raw_token")"
    token_length="${#NEW_TOKEN}"

    if [ "$token_length" -ge 8 ] && \
       [ "$token_length" -le 4096 ]; then
      return 0
    fi
  done
}


# ============================================================
# 通用可靠操作
# ============================================================

retry_operation() {
  attempts="$1"
  shift

  attempt=1

  while [ "$attempt" -le "$attempts" ]; do
    if "$@"; then
      return 0
    fi

    if [ "$attempt" -lt "$attempts" ]; then
      /bin/sleep "$attempt"
    fi

    attempt=$((attempt + 1))
  done

  return 1
}


# ============================================================
# 更新 CubeAI 自有配置
# ============================================================

update_cubeai_config() {
  token="$1"
  temporary="${CUBEAI_CONFIG_FILE}.tmp.$$"

  /bin/mkdir -p "$CUBEAI_DIR" 2>/dev/null || return 1
  /bin/chmod 700 "$CUBEAI_DIR" 2>/dev/null || true

  CUBEAI_CONFIG_PATH="$CUBEAI_CONFIG_FILE" \
  CUBEAI_TEMP_PATH="$temporary" \
  CUBEAI_BASE_VALUE="$CUBEAI_BASE_URL" \
  CUBEAI_TOKEN_VALUE="$token" \
  /usr/bin/osascript -l JavaScript >/dev/null 2>&1 <<'JXA'
ObjC.import("Foundation");

function envValue(name) {
  var value = $.NSProcessInfo.processInfo.environment.objectForKey(name);

  if (value === null || value === undefined) {
    throw new Error("missing environment value");
  }

  return String(ObjC.unwrap(value));
}

function readText(path) {
  if (!$.NSFileManager.defaultManager.fileExistsAtPath(path)) {
    return null;
  }

  var value = $.NSString.stringWithContentsOfFileEncodingError(
    path,
    $.NSUTF8StringEncoding,
    null
  );

  if (value === null || value === undefined) {
    return null;
  }

  return String(ObjC.unwrap(value));
}

function writeText(path, text) {
  return $(text).writeToFileAtomicallyEncodingError(
    path,
    true,
    $.NSUTF8StringEncoding,
    null
  );
}

var configPath = envValue("CUBEAI_CONFIG_PATH");
var temporaryPath = envValue("CUBEAI_TEMP_PATH");
var config = {};
var original = readText(configPath);

if (typeof original === "string" && original.trim() !== "") {
  try {
    var parsed = JSON.parse(original);

    if (
      parsed !== null &&
      !Array.isArray(parsed) &&
      typeof parsed === "object"
    ) {
      config = parsed;
    }
  } catch (error) {
    config = {};
  }
}

config.product = "CubeAI";
config.base_url = envValue("CUBEAI_BASE_VALUE");
config.token = envValue("CUBEAI_TOKEN_VALUE");

if (!writeText(
  temporaryPath,
  JSON.stringify(config, null, 2) + "\n"
)) {
  throw new Error("write failed");
}
JXA

  result=$?

  if [ "$result" -ne 0 ] || [ ! -s "$temporary" ]; then
    /bin/rm -f "$temporary" 2>/dev/null || true
    return 1
  fi

  /bin/chmod 600 "$temporary" 2>/dev/null || true

  /bin/mv -f "$temporary" "$CUBEAI_CONFIG_FILE" \
    2>/dev/null || {
      /bin/rm -f "$temporary" 2>/dev/null || true
      return 1
    }

  /bin/chmod 600 "$CUBEAI_CONFIG_FILE" 2>/dev/null || true
  return 0
}


# ============================================================
# 只更新 Claude Code 地址和密钥
# ============================================================

update_claude_settings() {
  token="$1"
  temporary="${CLAUDE_SETTINGS_FILE}.tmp.$$"

  /bin/mkdir -p "$CLAUDE_DIR" 2>/dev/null || return 1
  /bin/chmod 700 "$CLAUDE_DIR" 2>/dev/null || true

  CLAUDE_SETTINGS_PATH="$CLAUDE_SETTINGS_FILE" \
  CLAUDE_TEMP_PATH="$temporary" \
  CUBEAI_BASE_VALUE="$CUBEAI_BASE_URL" \
  CUBEAI_TOKEN_VALUE="$token" \
  /usr/bin/osascript -l JavaScript >/dev/null 2>&1 <<'JXA'
ObjC.import("Foundation");

function envValue(name) {
  var value = $.NSProcessInfo.processInfo.environment.objectForKey(name);

  if (value === null || value === undefined) {
    throw new Error("missing environment value");
  }

  return String(ObjC.unwrap(value));
}

function readText(path) {
  if (!$.NSFileManager.defaultManager.fileExistsAtPath(path)) {
    return null;
  }

  var value = $.NSString.stringWithContentsOfFileEncodingError(
    path,
    $.NSUTF8StringEncoding,
    null
  );

  if (value === null || value === undefined) {
    return null;
  }

  return String(ObjC.unwrap(value));
}

function writeText(path, text) {
  return $(text).writeToFileAtomicallyEncodingError(
    path,
    true,
    $.NSUTF8StringEncoding,
    null
  );
}

var settingsPath = envValue("CLAUDE_SETTINGS_PATH");
var temporaryPath = envValue("CLAUDE_TEMP_PATH");
var settings = {};
var original = readText(settingsPath);

if (typeof original === "string" && original.trim() !== "") {
  try {
    settings = JSON.parse(original);

    if (
      settings === null ||
      Array.isArray(settings) ||
      typeof settings !== "object"
    ) {
      throw new Error("invalid settings object");
    }
  } catch (error) {
    throw new Error("invalid settings json");
  }
}

if (
  settings.env === null ||
  Array.isArray(settings.env) ||
  typeof settings.env !== "object"
) {
  settings.env = {};
}

// 更换密钥只修改这两项。
// 模型选择、MCP、权限、插件和 Hooks 全部保留。
settings.env.ANTHROPIC_BASE_URL =
  envValue("CUBEAI_BASE_VALUE");
settings.env.ANTHROPIC_AUTH_TOKEN =
  envValue("CUBEAI_TOKEN_VALUE");

if (!writeText(
  temporaryPath,
  JSON.stringify(settings, null, 2) + "\n"
)) {
  throw new Error("write failed");
}
JXA

  result=$?

  if [ "$result" -ne 0 ] || [ ! -s "$temporary" ]; then
    /bin/rm -f "$temporary" 2>/dev/null || true
    return 1
  fi

  /bin/chmod 600 "$temporary" 2>/dev/null || true

  /bin/mv -f "$temporary" "$CLAUDE_SETTINGS_FILE" \
    2>/dev/null || {
      /bin/rm -f "$temporary" 2>/dev/null || true
      return 1
    }

  /bin/chmod 600 "$CLAUDE_SETTINGS_FILE" 2>/dev/null || true
  return 0
}


# ============================================================
# 更新已经存在的 CubeAI 启动脚本
# ============================================================

update_existing_script() {
  target="$1"
  token="$2"

  [ -f "$target" ] || return 0

  first_two="$(
    /usr/bin/head -c 2 "$target" 2>/dev/null || true
  )"

  [ "$first_two" = "#!" ] || return 0

  quoted_token="$(printf '%q' "$token")"
  temporary="${target}.tmp.$$"

  SCRIPT_PATH_VALUE="$target" \
  SCRIPT_TEMP_VALUE="$temporary" \
  TOKEN_VALUE="$quoted_token" \
  BASE_VALUE="$CUBEAI_BASE_URL" \
  /usr/bin/osascript -l JavaScript >/dev/null 2>&1 <<'JXA'
ObjC.import("Foundation");

function envValue(name) {
  var value = $.NSProcessInfo.processInfo.environment.objectForKey(name);

  if (value === null || value === undefined) {
    throw new Error("missing environment value");
  }

  return String(ObjC.unwrap(value));
}

function readText(path) {
  var value = $.NSString.stringWithContentsOfFileEncodingError(
    path,
    $.NSUTF8StringEncoding,
    null
  );

  if (value === null || value === undefined) {
    return null;
  }

  return String(ObjC.unwrap(value));
}

function writeText(path, text) {
  return $(text).writeToFileAtomicallyEncodingError(
    path,
    true,
    $.NSUTF8StringEncoding,
    null
  );
}

var targetPath = envValue("SCRIPT_PATH_VALUE");
var temporaryPath = envValue("SCRIPT_TEMP_VALUE");
var token = envValue("TOKEN_VALUE");
var base = envValue("BASE_VALUE");
var text = readText(targetPath);

if (typeof text !== "string") {
  throw new Error("read failed");
}

// 新版 shim 直接导出密钥；旧版启动器可能先保存 TOKEN 变量。
// 只替换已经存在的密钥行，不加入任何模型或其他配置。
text = text.replace(
  /^TOKEN=.*$/gm,
  "TOKEN=" + token
);

text = text.replace(
  /^export[ \t]+ANTHROPIC_AUTH_TOKEN=.*$/gm,
  "export ANTHROPIC_AUTH_TOKEN=" + token
);

text = text.replace(
  /^export[ \t]+ANTHROPIC_BASE_URL=.*$/gm,
  'export ANTHROPIC_BASE_URL="' + base + '"'
);

if (!writeText(temporaryPath, text)) {
  throw new Error("write failed");
}
JXA

  result=$?

  if [ "$result" -ne 0 ] || [ ! -s "$temporary" ]; then
    /bin/rm -f "$temporary" 2>/dev/null || true
    return 0
  fi

  /bin/chmod 700 "$temporary" 2>/dev/null || true

  /bin/mv -f "$temporary" "$target" \
    2>/dev/null || {
      /bin/rm -f "$temporary" 2>/dev/null || true
      return 0
    }

  /bin/chmod 700 "$target" 2>/dev/null || true
  return 0
}

update_existing_launchers() {
  token="$1"

  update_existing_script "$CLAUDE_SHIM" "$token"
  update_existing_script "$CUBEAI_OLD_LAUNCHER" "$token"
}


# ============================================================
# 写入校验
# ============================================================

verify_updated_config() {
  token="$1"

  CUBEAI_CONFIG_PATH="$CUBEAI_CONFIG_FILE" \
  CLAUDE_SETTINGS_PATH="$CLAUDE_SETTINGS_FILE" \
  CUBEAI_BASE_VALUE="$CUBEAI_BASE_URL" \
  CUBEAI_TOKEN_VALUE="$token" \
  /usr/bin/osascript -l JavaScript >/dev/null 2>&1 <<'JXA'
ObjC.import("Foundation");

function envValue(name) {
  var value = $.NSProcessInfo.processInfo.environment.objectForKey(name);

  if (value === null || value === undefined) {
    throw new Error("missing environment value");
  }

  return String(ObjC.unwrap(value));
}

function readJson(path) {
  if (!$.NSFileManager.defaultManager.fileExistsAtPath(path)) {
    throw new Error("missing file");
  }

  var value = $.NSString.stringWithContentsOfFileEncodingError(
    path,
    $.NSUTF8StringEncoding,
    null
  );

  if (value === null || value === undefined) {
    throw new Error("read failed");
  }

  return JSON.parse(String(ObjC.unwrap(value)));
}

var expectedBase = envValue("CUBEAI_BASE_VALUE");
var expectedToken = envValue("CUBEAI_TOKEN_VALUE");

var config = readJson(envValue("CUBEAI_CONFIG_PATH"));
var settings = readJson(envValue("CLAUDE_SETTINGS_PATH"));

if (
  config.base_url !== expectedBase ||
  config.token !== expectedToken
) {
  throw new Error("config mismatch");
}

if (
  settings.env === null ||
  typeof settings.env !== "object" ||
  settings.env.ANTHROPIC_BASE_URL !== expectedBase ||
  settings.env.ANTHROPIC_AUTH_TOKEN !== expectedToken
) {
  throw new Error("settings mismatch");
}
JXA
}


# ============================================================
# 找到并启动现有 Claude Code
# ============================================================

locate_claude() {
  candidates="
${CLAUDE_SHIM}
${CUBEAI_MANAGED_CLAUDE}
${USER_HOME}/.claude/bin/claude
/opt/homebrew/bin/claude
/usr/local/bin/claude
"

  path_from_command="$(command -v claude 2>/dev/null || true)"

  if [ -n "$path_from_command" ]; then
    candidates="${candidates}
${path_from_command}"
  fi

  old_ifs="$IFS"
  IFS='
'

  for candidate in $candidates; do
    [ -n "$candidate" ] || continue

    if [ -f "$candidate" ] && [ -x "$candidate" ]; then
      CLAUDE_PATH="$candidate"
      IFS="$old_ifs"
      return 0
    fi
  done

  IFS="$old_ifs"
  CLAUDE_PATH=""
  return 1
}

schedule_old_window_close() {
  window_id="$1"

  case "$window_id" in
    ""|*[!0-9]*)
      return 0
      ;;
  esac

  /usr/bin/nohup /bin/bash -c "
    /bin/sleep 2
    /usr/bin/osascript >/dev/null 2>&1 <<'APPLESCRIPT'
tell application \"Terminal\"
  repeat with currentWindow in windows
    try
      if (id of currentWindow) is ${window_id} then
        if (count of tabs of currentWindow) is 1 then
          close currentWindow
        end if
        exit repeat
      end if
    end try
  end repeat
end tell
APPLESCRIPT
  " >/dev/null 2>&1 &
}

open_new_terminal() {
  claude_path="$1"

  first_two="$(
    /usr/bin/head -c 2 "$claude_path" 2>/dev/null || true
  )"

  if [ "$first_two" = "#!" ]; then
    launch_kind="script"
  else
    launch_kind="binary"
  fi

  CLAUDE_PATH_VALUE="$claude_path" \
  CLAUDE_KIND_VALUE="$launch_kind" \
  /usr/bin/osascript >/dev/null 2>&1 <<'APPLESCRIPT'
set claudePath to system attribute "CLAUDE_PATH_VALUE"
set launchKind to system attribute "CLAUDE_KIND_VALUE"

tell application "Terminal"
  activate

  if launchKind is "script" then
    do script "/bin/bash " & quoted form of claudePath
  else
    do script quoted form of claudePath
  end if
end tell
APPLESCRIPT

  result=$?

  if [ "$result" -eq 0 ]; then
    schedule_old_window_close "$OLD_TERMINAL_WINDOW_ID"
    return 0
  fi

  return 1
}


# ============================================================
# 总流程
# ============================================================

main() {
  if [ "$(uname -s 2>/dev/null || true)" != "Darwin" ]; then
    exit 1
  fi

  capture_current_terminal_window
  read_new_token

  clear_screen
  draw_progress 5

  retry_operation 3 update_cubeai_config "$NEW_TOKEN" || {
    draw_progress 5 "请稍后重试"
    printf '\n'
    /bin/sleep 4
    exit 1
  }

  draw_progress 38

  retry_operation 3 update_claude_settings "$NEW_TOKEN" || {
    draw_progress 38 "请稍后重试"
    printf '\n'
    /bin/sleep 4
    exit 1
  }

  draw_progress 72

  update_existing_launchers "$NEW_TOKEN"
  draw_progress 88

  retry_operation 2 verify_updated_config "$NEW_TOKEN" || {
    draw_progress 88 "请稍后重试"
    printf '\n'
    /bin/sleep 4
    exit 1
  }

  draw_progress 100

  if locate_claude && open_new_terminal "$CLAUDE_PATH"; then
    /bin/sleep 1
    exit 0
  fi

  # 不安装、不修复。若新终端无法创建，仅在当前窗口启动现有 Claude。
  if [ -n "${CLAUDE_PATH:-}" ]; then
    printf '\n'
    exec "$CLAUDE_PATH"
  fi

  printf '\n'
  /bin/sleep 2
  exit 0
}

main
