#!/bin/bash
# Claude Key Tool (Mac) — double-click hoặc: bash ClaudeKeyTool.command sk-...
set -euo pipefail
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

VERSION="1.3.4"
CONFIG_ID="7c4a2f91-3b8e-4d6a-9f21-a1b2c3d4e5f6"
GATEWAY="https://api.nghimmo.com"
LIB="$HOME/Library/Application Support/Claude-3p/configLibrary"
MARKER="$HOME/Library/Application Support/Claude-3p/.claude-key-tool.json"
SETTINGS="$HOME/.claude/settings.json"
CLAUDE_JSON="$HOME/.claude.json"
LOG="$HOME/Library/Logs/claude-key-tool.log"

log() {
  mkdir -p "$(dirname "$LOG")"
  printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$LOG"
}

say() {
  echo "[Claude Key Tool] $*" >&2
  log "$*"
}

alert() {
  say "$1"
  osascript -e "display alert \"Claude Key Tool\" message \"$1\" as ${2:-informational}" >/dev/null 2>&1 || true
}

maybe_self_update() {
  for _a in "$@"; do
    case "$_a" in --no-update|--skip-update) return 0 ;; esac
  done
  [ -n "${CLAUDE_KEY_TOOL_NO_UPDATE:-}" ] && return 0
  command -v python3 >/dev/null 2>&1 || return 0
  local self rc
  self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
  # set -e: capture python exit without aborting (99 = update scheduled)
  set +e
  VERSION="$VERSION" GATEWAY="$GATEWAY" SELF="$self" python3 - "$@" <<'PY'
import json, os, sys, urllib.request, hashlib, subprocess
ver = os.environ["VERSION"]
gw = os.environ["GATEWAY"].rstrip("/")
self = os.environ["SELF"]
args = sys.argv[1:]
try:
    with urllib.request.urlopen(gw + "/ClaudeKeyTool-version.json", timeout=12) as r:
        man = json.loads(r.read().decode())
except Exception:
    raise SystemExit(0)
remote = str(man.get("version") or "")
def parse(v):
    out = []
    for p in str(v).split("."):
        digits = "".join(c for c in p if c.isdigit())
        out.append(int(digits or "0"))
    return out or [0]
a, b = parse(remote), parse(ver)
n = max(len(a), len(b))
a += [0] * (n - len(a))
b += [0] * (n - len(b))
if a <= b:
    raise SystemExit(0)
plat = "macos" if sys.platform == "darwin" else "linux"
info = man.get(plat) or {}
url = info.get("url")
if not url:
    raise SystemExit(0)
tmp = self + "." + remote + ".new"
try:
    urllib.request.urlretrieve(url, tmp)
except Exception:
    raise SystemExit(0)
sha = info.get("sha256")
if sha:
    h = hashlib.sha256(open(tmp, "rb").read()).hexdigest()
    if h.lower() != str(sha).lower():
        os.remove(tmp)
        raise SystemExit(0)
helper = self + ".apply-update.sh"
parts = []
for a in args:
    parts.append("'" + a.replace("'", "'\"'\"'") + "'")
qargs = " ".join(parts)
open(helper, "w", encoding="utf-8").write(
    "#!/bin/bash\n"
    "sleep 1\n"
    f'mv -f "{tmp}" "{self}"\n'
    f'chmod +x "{self}"\n'
    f'rm -f "{helper}"\n'
    f'exec "{self}" {qargs}\n'
)
os.chmod(helper, 0o755)
subprocess.Popen(["/bin/bash", helper], start_new_session=True)
print("UPDATE", remote, flush=True)
raise SystemExit(99)
PY
  rc=$?
  set -e
  if [ "$rc" = "99" ]; then
    say "Đã tải bản mới — khởi động lại tool…" 2>/dev/null || echo "[Claude Key Tool] updating…" >&2
    exit 0
  fi
}

gui_activate() {
  osascript -e 'tell application "Terminal" to activate' >/dev/null 2>&1 || true
}

pause_if_needed() {
  if [ -t 0 ] && [ -r /dev/tty ]; then
    read -r -p "Nhấn Enter để đóng..." _ </dev/tty 2>/dev/null || true
  fi
}

confirm() {
  local msg="$1"
  gui_activate
  if osascript -e "display dialog \"$msg\" buttons {\"Huỷ\", \"OK\"} default button \"OK\" with icon caution" 2>/dev/null | grep -q "OK"; then
    return 0
  fi
  read -r -p "$msg [y/N]: " ans </dev/tty 2>/dev/null || ans=""
  case "${ans:-N}" in
    y|Y|yes|Yes) return 0 ;;
    *) return 1 ;;
  esac
}

enable_dev_mode() {
  local body='{
  "allowDevTools": true
}'
  for p in \
    "$HOME/Library/Application Support/Claude/developer_settings.json" \
    "$HOME/Library/Application Support/Claude-3p/developer_settings.json"
  do
    mkdir -p "$(dirname "$p")"
    printf '%s\n' "$body" >"$p"
  done
}

clear_extension_cache() {
  local rel="User/globalStorage/anthropic.claude-code"
  for base in "$HOME/Library/Application Support/Code" \
              "$HOME/Library/Application Support/Cursor" \
              "$HOME/Library/Application Support/Code - Insiders" \
              "$HOME/Library/Application Support/Antigravity" \
              "$HOME/Library/Application Support/VSCodium" \
              "$HOME/Library/Application Support/Windsurf" \
              "$HOME/Library/Application Support/Trae"
  do
    if [ -d "$base/$rel" ]; then
      rm -rf "$base/$rel"
      log "cleared extension cache: $base/$rel"
    fi
  done
}

clear_claude_session() {
  local base rel
  for base in \
    "$HOME/Library/Application Support/Claude-3p" \
    "$HOME/Library/Application Support/Claude"
  do
    [ -d "$base" ] || continue
    for rel in Cookies "Local Storage" "Session Storage" Cache "Code Cache"; do
      if [ -e "$base/$rel" ]; then
        rm -rf "$base/$rel"
        log "cleared session: $base/$rel"
      fi
    done
  done
}

write_desktop_config() {
  local key="$1"
  mkdir -p "$LIB" "$(dirname "$MARKER")"
  if command -v python3 >/dev/null 2>&1; then
    LIB="$LIB" MARKER="$MARKER" CONFIG_ID="$CONFIG_ID" GATEWAY="$GATEWAY" KEY="$key" VERSION="$VERSION" python3 <<'PY'
import json, os
from pathlib import Path
lib = Path(os.environ["LIB"])
cid = os.environ["CONFIG_ID"]
key = os.environ["KEY"]
gw = os.environ["GATEWAY"]
ver = os.environ["VERSION"]
cfg = {
    "inferenceProvider": "gateway",
    "inferenceGatewayBaseUrl": gw,
    "inferenceGatewayApiKey": key,
    "inferenceGatewayAuthScheme": "x-api-key",
    "inferenceCredentialKind": "static",
    "modelDiscoveryEnabled": True,
    "inferenceModels": [
        {"name": "nghi/claude-opus-5.5[1m]"},
        {"name": "nghi/claude-opus-5[1m]"},
        {"name": "nghi/claude-opus-4.8[1m]"},
        {"name": "nghi/claude-opus-4.7[1m]"},
        {"name": "nghi/claude-opus-4.6[1m]"},
        {"name": "nghi/claude-fable-5.1[1m]"},
        {"name": "nghi/claude-fable-5[1m]"},
        {"name": "nghi/claude-sonnet-5[1m]"},
        {"name": "nghi/claude-haiku-4.5[1m]"},
    ],
    "chatTabEnabled": True,
    "coworkTabEnabled": True,
    "isClaudeCodeForDesktopEnabled": True,
}
meta = {"appliedId": cid, "entries": [{"id": cid, "name": "Claude Key Tool"}]}
(lib / f"{cid}.json").write_text(json.dumps(cfg, indent=2) + "\n", encoding="utf-8")
(lib / "_meta.json").write_text(json.dumps(meta, indent=2) + "\n", encoding="utf-8")
Path(os.environ["MARKER"]).write_text(json.dumps({"version": ver, "key": key, "gateway": gw}, indent=2) + "\n", encoding="utf-8")
PY
  else
    local esc
    esc=$(printf '%s' "$key" | sed 's/\\/\\\\/g; s/"/\\"/g')
    cat >"$LIB/$CONFIG_ID.json" <<EOF
{
  "inferenceProvider": "gateway",
  "inferenceGatewayBaseUrl": "$GATEWAY",
  "inferenceGatewayApiKey": "$esc",
  "inferenceGatewayAuthScheme": "x-api-key",
  "inferenceCredentialKind": "static",
  "modelDiscoveryEnabled": true,
  "inferenceModels": [
    {"name": "nghi/claude-opus-5.5[1m]"},
    {"name": "nghi/claude-opus-5[1m]"},
    {"name": "nghi/claude-opus-4.8[1m]"},
    {"name": "nghi/claude-opus-4.7[1m]"},
    {"name": "nghi/claude-opus-4.6[1m]"},
    {"name": "nghi/claude-fable-5.1[1m]"},
    {"name": "nghi/claude-fable-5[1m]"},
    {"name": "nghi/claude-sonnet-5[1m]"},
    {"name": "nghi/claude-haiku-4.5[1m]"}
  ],
  "chatTabEnabled": true,
  "coworkTabEnabled": true,
  "isClaudeCodeForDesktopEnabled": true
}
EOF
    cat >"$LIB/_meta.json" <<EOF
{
  "appliedId": "$CONFIG_ID",
  "entries": [
    {
      "id": "$CONFIG_ID",
      "name": "Claude Key Tool"
    }
  ]
}
EOF
    printf '{\n  "version": "%s",\n  "key": "%s",\n  "gateway": "%s"\n}\n' "$VERSION" "$esc" "$GATEWAY" >"$MARKER"
  fi
}

write_claude_code() {
  local key="$1"
  mkdir -p "$(dirname "$SETTINGS")"
  # Always clear IDE extension cache (VS Code / Cursor / Antigravity / …)
  # so Reload Window picks up new ~/.claude/settings.json.
  clear_extension_cache
  if command -v python3 >/dev/null 2>&1; then
    SETTINGS="$SETTINGS" CLAUDE_JSON="$CLAUDE_JSON" GATEWAY="$GATEWAY" KEY="$key" python3 <<'PY'
import json, os
from pathlib import Path
sf = Path(os.environ["SETTINGS"])
data = {}
if sf.exists():
    try:
        data = json.loads(sf.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        data = {}
if not isinstance(data, dict):
    data = {}
env = data.get("env")
if not isinstance(env, dict):
    env = {}
env["ANTHROPIC_BASE_URL"] = os.environ["GATEWAY"]
env["ANTHROPIC_AUTH_TOKEN"] = os.environ["KEY"]
env["ANTHROPIC_MODEL"] = "nghi/claude-sonnet-5"
env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "nghi/claude-opus-5"
env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "nghi/claude-sonnet-5"
env["ANTHROPIC_SMALL_FAST_MODEL"] = "nghi/claude-haiku-4.5"
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "nghi/claude-haiku-4.5"
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1"
env["CLAUDE_CODE_USE_GATEWAY"] = "1"
env["CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK"] = "1"
env["API_TIMEOUT_MS"] = "600000"
env.pop("ANTHROPIC_API_KEY", None)
data["env"] = env
sf.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
cj = Path(os.environ["CLAUDE_JSON"])
if cj.exists():
    try:
        cdata = json.loads(cj.read_text(encoding="utf-8"))
        if isinstance(cdata, dict):
            for k in ("primaryApiKey", "customApiKeyResponses"):
                cdata.pop(k, None)
            cj.write_text(json.dumps(cdata, indent=2) + "\n", encoding="utf-8")
    except json.JSONDecodeError:
        pass
PY
  else
    cat >"$SETTINGS" <<EOF
{
  "env": {
    "ANTHROPIC_BASE_URL": "$GATEWAY",
    "ANTHROPIC_AUTH_TOKEN": "$key",
    "ANTHROPIC_MODEL": "nghi/claude-sonnet-5",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "nghi/claude-opus-5",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "nghi/claude-sonnet-5",
    "ANTHROPIC_SMALL_FAST_MODEL": "nghi/claude-haiku-4.5",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "nghi/claude-haiku-4.5",
    "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1",
    "CLAUDE_CODE_USE_GATEWAY": "1",
    "CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK": "1",
    "API_TIMEOUT_MS": "600000"
  }
}
EOF
  fi
}

reset_all() {
  rm -f "$LIB/$CONFIG_ID.json" "$MARKER"
  if [ -f "$LIB/_meta.json" ]; then
    printf '{\n  "appliedId": "",\n  "entries": []\n}\n' >"$LIB/_meta.json"
  fi
  clear_extension_cache
  if command -v python3 >/dev/null 2>&1 && [ -f "$SETTINGS" ]; then
    SETTINGS="$SETTINGS" CLAUDE_JSON="$CLAUDE_JSON" python3 <<'PY'
import json, os
from pathlib import Path
for path_key in ("SETTINGS", "CLAUDE_JSON"):
    p = Path(os.environ[path_key])
    if not p.exists():
        continue
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        continue
    if path_key == "SETTINGS" and isinstance(data.get("env"), dict):
        for k in ("ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_SMALL_FAST_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL", "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY", "CLAUDE_CODE_USE_GATEWAY", "CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK", "API_TIMEOUT_MS"):
            data["env"].pop(k, None)
    if path_key == "CLAUDE_JSON" and isinstance(data, dict):
        for k in ("primaryApiKey", "customApiKeyResponses"):
            data.pop(k, None)
    p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
  fi
}

pick_action_terminal() {
  echo ""
  echo "  1) Áp dụng key"
  echo "  2) Reset cấu hình"
  echo "  3) Thoát"
  read -r -p "Chọn [1]: " choice </dev/tty 2>/dev/null || choice="1"
  case "${choice:-1}" in
    2) printf 'reset' ;;
    3) printf 'exit' ;;
    *) printf 'apply' ;;
  esac
}

pick_action() {
  gui_activate
  local out
  out="$(osascript <<'APPLE' 2>/dev/null || true
set choices to {"Ap dung key", "Reset cau hinh", "Thoat"}
set picked to choose from list choices with prompt "Claude Key Tool — chon thao tac:" default items {"Ap dung key"}
if picked is false then return ""
if item 1 of picked is "Reset cau hinh" then return "reset"
if item 1 of picked is "Thoat" then return "exit"
return "apply"
APPLE
)"
  out="$(printf '%s' "$out" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
  case "$out" in
    apply|reset|exit) printf '%s' "$out" ;;
    *)
      say "Không mở được hộp thoại — chọn trong Terminal:"
      pick_action_terminal
      ;;
  esac
}

ask_key() {
  local preset="${1:-}"
  if [ -n "$preset" ]; then
    printf '%s' "$preset"
    return
  fi
  gui_activate
  local out
  out="$(osascript <<'APPLE' 2>/dev/null || true
set dlg to display dialog "Dan API key Claude (sk-...):" default answer "" with hidden answer buttons {"Huy", "OK"} default button "OK" with title "Claude Key Tool"
return text returned of dlg
APPLE
)"
  out="$(printf '%s' "$out" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
  if [ -n "$out" ]; then
    printf '%s' "$out"
    return
  fi
  say "Nhập key trong Terminal:"
  read -r -s -p "API key (sk-...): " out </dev/tty 2>/dev/null || out=""
  echo >&2
  printf '%s' "$out"
}

claude_running() {
  pgrep -x Claude >/dev/null 2>&1
}

quit_claude() {
  local i
  if claude_running; then
    log "quit: Claude running before quit"
  fi
  osascript -e 'tell application "Claude" to quit' >/dev/null 2>&1 || true
  sleep 1
  killall Claude >/dev/null 2>&1 || true
  pkill -x Claude >/dev/null 2>&1 || true
  for i in $(seq 1 20); do
    claude_running || break
    sleep 0.5
  done
  if claude_running; then
    log "warn: Claude still running after quit attempts"
  else
    log "quit: Claude stopped"
  fi
  sleep 3
}

verify_desktop_config() {
  [ -f "$LIB/$CONFIG_ID.json" ] && [ -f "$LIB/_meta.json" ] && [ -f "$MARKER" ]
}

is_gateway_configured() {
  [ -f "$LIB/$CONFIG_ID.json" ] && grep -q '"inferenceProvider"[[:space:]]*:[[:space:]]*"gateway"' "$LIB/$CONFIG_ID.json" 2>/dev/null
}

check_managed_profile() {
  [ -f "/Library/Managed Preferences/com.anthropic.claudefordesktop.plist" ]
}

open_claude() {
  if [ -d "/Applications/Claude.app" ]; then
    open -n -a "Claude"
    return 0
  fi
  if [ -d "$HOME/Applications/Claude.app" ]; then
    open -n -a "$HOME/Applications/Claude.app"
    return 0
  fi
  return 1
}

check_claude_app() {
  [ -d "/Applications/Claude.app" ] || [ -d "$HOME/Applications/Claude.app" ]
}

usage() {
  cat <<EOF
Claude Key Tool v$VERSION

Cách dùng:
  Double-click file .command
  bash "$0" --desktop sk-...
  bash "$0" --cli sk-...
  bash "$0" --reset --desktop
  bash "$0" --reset --cli

Desktop: model có [1m]. CLI: model không [1m] (tránh lỗi một số bản Claude Code).
Log: $LOG
EOF
}

# --- CLI ---
KEY_ARG=""
ACTION_ARG=""
MODE_ARG=""
while [ $# -gt 0 ]; do
  case "$1" in
    --desktop|--app) MODE_ARG=desktop; shift ;;
    --cli) MODE_ARG=cli; shift ;;
    --reset|-r) ACTION_ARG=reset; shift ;;
    --apply|-a)
      ACTION_ARG=apply
      KEY_ARG="${2:-}"
      shift 2
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    sk-*)
      ACTION_ARG=apply
      KEY_ARG="$1"
      shift
      ;;
    *)
      say "Bỏ qua tham số không rõ: $1"
      shift
      ;;
  esac
done

ask_mode() {
  if [ -n "$MODE_ARG" ]; then
    printf '%s' "$MODE_ARG"
    return
  fi
  local choice
  choice="$(osascript <<'OSA' 2>/dev/null || true
set r to button returned of (display dialog "Chọn nền tảng" buttons {"CLI", "Desktop", "Huỷ"} default button "Desktop" with title "Claude Key Tool")
return r
OSA
)"
  case "$choice" in
    CLI|cli) printf 'cli' ;;
    Desktop|desktop) printf 'desktop' ;;
    *) printf '' ;;
  esac
}

MODE="$(ask_mode)"
if [ -z "$MODE" ]; then
  say "Đã huỷ."
  exit 0
fi
say "Mode: $MODE"

maybe_self_update "$@"

# --- main ---
say "=== Claude Key Tool v$VERSION (user=$USER) ==="
log "=== start pid $$ home=$HOME ==="

if [ -n "$ACTION_ARG" ]; then
  action="$ACTION_ARG"
else
  action="$(pick_action)"
fi

case "$action" in
  exit)
    say "Đã huỷ."
    pause_if_needed
    exit 0
    ;;
  reset)
    if confirm "Xóa HẾT cấu hình Claude Desktop + Claude Code CLI trên máy này?"; then
      reset_all
      rm -f "$HOME/.claude/.claude-key-tool-cli.json"
      alert "Đã xóa config Desktop + CLI.\n\nKhởi động lại Claude Desktop / reload VS Code nếu đang mở." "informational"
    else
      say "Reset đã huỷ."
    fi
    pause_if_needed
    exit 0
    ;;
  apply|*) ;;
esac

key="$(ask_key "$KEY_ARG")"
key="$(printf '%s' "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"

if [ -z "$key" ]; then
  say "Chưa nhập key."
  alert "Chưa nhập key." "warning"
  pause_if_needed
  exit 1
fi
if [ "${#key}" -lt 8 ] || [[ "$key" == *YOUR_API_KEY* ]]; then
  say "Key không hợp lệ."
  alert "Key không hợp lệ. Kiểm tra lại." "warning"
  pause_if_needed
  exit 1
fi

if [ "$MODE" = "cli" ]; then
  say "Đang áp dụng key CLI (model không [1m])..."
  already=0
  if [ -f "$SETTINGS" ] && grep -q "ANTHROPIC_AUTH_TOKEN" "$SETTINGS" 2>/dev/null; then
    already=1
  fi
  write_claude_code "$key"
  mkdir -p "$HOME/.claude"
  printf '{\n  "version": "%s",\n  "mode": "cli",\n  "key": "%s",\n  "gateway": "%s"\n}\n' "$VERSION" "$key" "$GATEWAY" >"$HOME/.claude/.claude-key-tool-cli.json"
  if [ "$already" -eq 1 ]; then
    alert "Đã đổi key CLI.\n\nReload Window trong Antigravity/VS Code/Cursor (Cmd+Shift+P). Chạy: claude" "informational"
  else
    alert "Đã ghi ~/.claude/settings.json.\n\nReload Window trong Antigravity/VS Code/Cursor. Model CLI không [1m]." "informational"
  fi
  log "=== done cli ==="
  pause_if_needed
  exit 0
fi

if ! check_claude_app; then
  say "Chưa cài Claude Desktop."
  alert "Chưa cài Claude Desktop.\n\nTải từ claude.com/download rồi chạy lại tool." "warning"
  pause_if_needed
  exit 1
fi

if check_managed_profile; then
  alert "Máy có profile MDM quản lý Claude Desktop." "warning"
  pause_if_needed
  exit 1
fi

say "Đang áp dụng key Desktop (model có [1m])..."
quit_claude
already=0
if is_gateway_configured; then
  already=1
else
  clear_claude_session
fi
enable_dev_mode
write_desktop_config "$key"

if ! verify_desktop_config; then
  alert "Không ghi được cấu hình Claude Desktop." "warning"
  pause_if_needed
  exit 1
fi

say "Đã ghi config Desktop"
if open_claude; then
  if [ "$already" -eq 1 ]; then
    alert "Xong! Đã đổi key Desktop." "informational"
  else
    alert "Xong! Chọn Continue with Gateway — không đăng nhập claude.ai." "informational"
  fi
else
  alert "Xong! Mở Claude Desktop → Continue with Gateway." "informational"
fi
log "=== done desktop ==="
pause_if_needed
