#!/usr/bin/env bash
# Code2AI 终端助手（c2a）
# 由 install.sh 安装到 ~/.local/bin/c2a；自更新源：https://console.code2ai.codes/c2a.sh
# 用法：
#   c2a            交互菜单
#   c2a doctor     环境自检（CC 安装 / 环境变量 / Key 有效性 / 网络路径 / API 连通）
#   c2a fix-ipv6   IPv6 线路不通时强制走 IPv4（c2a fix-ipv6 undo 撤销）
#   c2a login      重新浏览器授权（更换账号或 Key 失效时用）
#   c2a update     更新 Claude CLI + c2a 自身
#   c2a uninstall  移除 c2a 与环境变量配置（保留 Claude CLI 本体）
#   c2a version    显示版本

set -u

C2A_VERSION="1.0.11"
BASE_URL="https://code2ai.codes"
API_HOST="code2ai.codes"
CONSOLE_URL="https://console.code2ai.codes"
MARKER_BEGIN="# >>> code2ai >>>"
MARKER_END="# <<< code2ai <<<"
HOSTS_FILE="/etc/hosts"
HOSTS_MARKER="# code2ai-force-ipv4"

RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; CYAN=$'\033[0;36m'; NC=$'\033[0m'
info()  { printf "${CYAN}==>${NC} %s\n" "$*"; }
ok()    { printf "${GREEN}✓${NC} %s\n" "$*"; }
warn()  { printf "${YELLOW}⚠${NC}  %s\n" "$*"; }
err()   { printf "${RED}✗${NC} %s\n" "$*" >&2; }

# ---------- 公共工具 ----------

# 从单行 JSON 中提取字符串字段（FastAPI 返回无空格单行 JSON，够用且零依赖）
json_field() {
  # $1=json $2=字段名
  printf '%s' "$1" | sed -n "s/.*\"$2\":\"\\([^\"]*\\)\".*/\\1/p"
}

json_field_num() {
  printf '%s' "$1" | sed -n "s/.*\"$2\":\\([0-9][0-9]*\\).*/\\1/p"
}

# best-effort 复制到剪贴板：WSL 用 clip.exe（Windows 剪贴板互通），macOS 用 pbcopy，
# Linux 桌面用 xclip/xsel；都没有则失败（调用方静默处理）
copy_to_clipboard() {
  if command -v clip.exe >/dev/null 2>&1; then printf '%s' "$1" | clip.exe 2>/dev/null && return 0; fi
  if command -v pbcopy >/dev/null 2>&1; then printf '%s' "$1" | pbcopy 2>/dev/null && return 0; fi
  if command -v xclip >/dev/null 2>&1; then printf '%s' "$1" | xclip -selection clipboard 2>/dev/null && return 0; fi
  if command -v xsel >/dev/null 2>&1; then printf '%s' "$1" | xsel --clipboard --input 2>/dev/null && return 0; fi
  return 1
}

# 下载内容是不是 HTML（DNS 劫持/错误页），是则返回 1
is_valid_shell_script() {
  local file="$1"
  [ -s "$file" ] || return 1
  if head -c 2048 "$file" | grep -qiE '<\s*(!doctype|html|head|body|script|meta)\b'; then
    return 1
  fi
  return 0
}

detect_shell_rc() {
  case "$(uname -s)" in
    Darwin) SHELL_RC="${HOME}/.zshrc" ;;
    *)
      if [ -n "${ZSH_VERSION:-}" ] || [ "${SHELL##*/}" = "zsh" ]; then
        SHELL_RC="${HOME}/.zshrc"
      else
        SHELL_RC="${HOME}/.bashrc"
      fi
      ;;
  esac
}

# 当前生效的 Key：优先环境变量，其次 rc marker 块
current_key() {
  if [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ]; then
    printf '%s' "$ANTHROPIC_AUTH_TOKEN"
    return
  fi
  [ -f "${SHELL_RC}" ] || return 0
  sed -n "/${MARKER_BEGIN}/,/${MARKER_END}/p" "${SHELL_RC}" \
    | sed -n 's/^export ANTHROPIC_AUTH_TOKEN="\([^"]*\)".*/\1/p' | head -1
}

mask_key() {
  local k="$1"
  if [ "${#k}" -le 12 ]; then printf '%s' "$k"; else printf '%s...%s' "${k:0:11}" "${k: -4}"; fi
}

# ---------- 设备码授权（与 install.sh 同实现） ----------

device_auth_flow() {
  info "进入配对授权..."
  local resp
  resp="$(curl -fsS --max-time 15 -X POST "${CONSOLE_URL}/api/v1/device/start" \
    -H 'Content-Type: application/json' -d '{}' 2>/dev/null)" || {
    err "无法连接授权服务，请检查网络后重试"
    return 1
  }
  local device_code user_code verify_url interval
  device_code="$(json_field "$resp" device_code)"
  user_code="$(json_field "$resp" user_code)"
  verify_url="$(json_field "$resp" verification_url)"
  interval="$(json_field_num "$resp" interval)"; interval="${interval:-3}"
  if [ -z "$device_code" ] || [ -z "$user_code" ]; then
    err "授权服务返回异常，请稍后重试"
    return 1
  fi

  echo
  printf "   ┌─────────────────────────────────────────┐\n"
  printf "   │   配对码：  ${GREEN}%s${NC}                       │\n" "$user_code"
  printf "   └─────────────────────────────────────────┘\n"
  echo
  echo "   请在任意浏览器（电脑/手机均可）打开下面的地址，登录后输入上面的配对码："
  printf "   ${CYAN}%s${NC}\n" "$verify_url"
  echo
  # 不自动开浏览器：用户的登录态通常在自己常用的浏览器里（如 Windows 侧），
  # 由用户自行复制打开体验更好，也避免 WSL/无桌面环境弹出无登录态的浏览器
  if copy_to_clipboard "$user_code"; then
    ok "配对码已复制到剪贴板，打开网页后直接粘贴即可"
    echo
  fi

  info "等待网页确认（10 分钟内有效）..."
  local deadline status
  deadline=$(( $(date +%s) + 600 ))
  while [ "$(date +%s)" -lt "$deadline" ]; do
    sleep "$interval"
    resp="$(curl -fsS --max-time 10 -X POST "${CONSOLE_URL}/api/v1/device/token" \
      -H 'Content-Type: application/json' \
      -d "{\"device_code\":\"${device_code}\"}" 2>/dev/null)" || { printf '.'; continue; }
    status="$(json_field "$resp" status)"
    case "$status" in
      success)
        echo
        API_KEY="$(json_field "$resp" api_key)"
        ok "授权成功（账号：$(json_field "$resp" username)）"
        return 0
        ;;
      no_subscription)
        echo
        err "该账号暂无有效订阅"
        echo "  请先购买套餐：$(json_field "$resp" pricing_url)"
        echo "  购买后重新运行本命令即可"
        return 1
        ;;
      key_unavailable)
        echo
        err "账号密钥需要重新生成"
        echo "  请到控制台「API 密钥管理」重新生成：$(json_field "$resp" dashboard_url)"
        return 1
        ;;
      expired)
        echo
        err "授权已过期，请重新运行本命令"
        return 1
        ;;
      *) printf '.' ;;
    esac
  done
  echo
  err "等待授权超时（10 分钟），请重新运行本命令"
  return 1
}

# ---------- 写环境变量（与 install.sh 同实现） ----------

write_config() {
  # 安全校验：Key 只允许安全字符集，杜绝写入 rc 后被 source 时命令注入
  case "$API_KEY" in
    ""|*[!A-Za-z0-9_-]*)
      err "API Key 含非法字符或为空，已中止（未写入配置）"
      return 1
      ;;
  esac

  [ -f "${SHELL_RC}" ] || touch "${SHELL_RC}"

  if grep -q "${MARKER_BEGIN}" "${SHELL_RC}" 2>/dev/null; then
    local bak="${SHELL_RC}.code2ai.bak.$(date +%s)"
    cp "${SHELL_RC}" "${bak}"
    info "检测到旧配置，已备份到 ${bak}"
    sed -i.tmp "/${MARKER_BEGIN}/,/${MARKER_END}/d" "${SHELL_RC}"
    rm -f "${SHELL_RC}.tmp"
  fi

  cat >> "${SHELL_RC}" <<EOF

${MARKER_BEGIN}
export PATH="\$HOME/.local/bin:\$PATH"
export ANTHROPIC_BASE_URL="${BASE_URL}"
export ANTHROPIC_AUTH_TOKEN="${API_KEY}"
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
export ENABLE_TOOL_SEARCH=true
${MARKER_END}
EOF

  ok "已写入环境变量到 ${SHELL_RC}"

  export PATH="${HOME}/.local/bin:${PATH}"
  export ANTHROPIC_BASE_URL="${BASE_URL}"
  export ANTHROPIC_AUTH_TOKEN="${API_KEY}"
  export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
  export ENABLE_TOOL_SEARCH=true
}

# ---------- API 连通性测试 ----------
# $1=key；输出 HTTP 码，网络失败输出 000
probe_api() {
  local resp
  resp="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 \
    "${BASE_URL}/v1/messages" \
    -H 'Content-Type: application/json' \
    -H "x-api-key: $1" \
    -H 'anthropic-version: 2023-06-01' \
    -d '{"model":"claude-sonnet-4-20250514","max_tokens":10,"messages":[{"role":"user","content":"hi"}]}' 2>/dev/null)" || resp="000"
  printf '%s' "$resp"
}

# ---------- 网络路径探测 ----------
# /health 是公开端点，不需要 Key，用它单独验证网络层，把「网络不通」和「Key 有问题」分开
# $1=curl 协议族参数（-4 / -6 / 空）；输出 "http_code|remote_ip"，失败为 "000|"
probe_health() {
  local out
  out="$(curl ${1:-} -sS -o /dev/null -w '%{http_code}|%{remote_ip}' --max-time 12 \
    "${BASE_URL}/health" 2>/dev/null)" || out="000|"
  printf '%s' "$out"
}

# 大包探测：小包通、大包挂 = PMTU 黑洞（IPv6 隧道常见，MTU 1280/1400 且收不到 ICMP 分片通知）。
# 表现是「刚开始能聊，上下文一长就断」，只测 /health 小 GET 会完全漏判。
# 这里发一个约 24KB 的 POST，服务端返回什么码都不重要——能拿到响应就说明大包过得去。
probe_large() {
  local payload out
  payload="$(head -c 24000 /dev/zero 2>/dev/null | tr '\0' 'x')"
  [ -n "$payload" ] || { printf 'skip'; return; }
  out="$(curl ${1:-} -sS -o /dev/null -w '%{http_code}' --max-time 20 \
    -X POST "${BASE_URL}/health" \
    -H 'Content-Type: text/plain' \
    --data-binary "$payload" 2>/dev/null)" || out="000"
  printf '%s' "$out"
}

# 是否设置了代理环境变量（ConnectionRefused 的头号嫌疑：代理进程已退出但变量还在）
proxy_env_summary() {
  local out=""
  local v
  for v in HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy; do
    eval "local val=\${$v:-}"
    [ -n "${val:-}" ] && out="${out}${v}=${val} "
  done
  printf '%s' "$out"
}

# 取 API 域名当前的 IPv4 地址（多种解析工具兜底，输出一行一个）
resolve_ipv4() {
  if command -v dig >/dev/null 2>&1; then
    dig +short A "${API_HOST}" 2>/dev/null | grep -E '^[0-9]+(\.[0-9]+){3}$'
    return
  fi
  if command -v getent >/dev/null 2>&1; then
    getent ahostsv4 "${API_HOST}" 2>/dev/null | awk '{print $1}' | sort -u
    return
  fi
  if command -v nslookup >/dev/null 2>&1; then
    nslookup -type=A "${API_HOST}" 2>/dev/null | awk '/^Address: /{print $2}' \
      | grep -E '^[0-9]+(\.[0-9]+){3}$'
    return
  fi
  # 最后兜底：让 curl 自己解析并回报连上的地址
  curl -4 -sS -o /dev/null -w '%{remote_ip}' --max-time 12 "${BASE_URL}/health" 2>/dev/null
}

hosts_pinned_ip() {
  [ -f "${HOSTS_FILE}" ] || return 0
  grep -E "^[0-9]+(\.[0-9]+){3}[[:space:]]+${API_HOST}([[:space:]]|$).*${HOSTS_MARKER}" \
    "${HOSTS_FILE}" 2>/dev/null | awk '{print $1}' | head -1
}

# 改完 hosts 后清系统 DNS 缓存，best-effort，失败不影响主流程
flush_dns_cache() {
  case "$(uname -s)" in
    Darwin)
      sudo dscacheutil -flushcache >/dev/null 2>&1
      sudo killall -HUP mDNSResponder >/dev/null 2>&1
      ;;
    Linux)
      command -v resolvectl >/dev/null 2>&1 && sudo resolvectl flush-caches >/dev/null 2>&1
      ;;
  esac
  return 0
}

# ---------- 子命令 ----------

cmd_doctor() {
  local fails=0
  # 诊断摘要用（结尾拼成一行，方便用户直接粘给客服）
  local s_cli="none" s_net="?" s_proxy="none" s_api="?" s_console="?"

  # 1. Claude CLI
  if command -v claude >/dev/null 2>&1; then
    s_cli="$(claude --version 2>/dev/null | head -1 | awk '{print $1}' || echo unknown)"
    ok "Claude CLI 已安装（$(claude --version 2>/dev/null | head -1 || echo unknown)）"
  else
    err "Claude CLI 未安装 —— 运行安装命令：curl -fsSL ${CONSOLE_URL}/install.sh | bash"
    fails=$((fails+1))
  fi

  # 2. rc 配置块
  if [ -f "${SHELL_RC}" ] && grep -q "${MARKER_BEGIN}" "${SHELL_RC}" 2>/dev/null; then
    ok "环境变量配置块存在（${SHELL_RC}）"
  else
    err "未找到 code2ai 配置块（${SHELL_RC}）—— 运行 c2a login 重新配置"
    fails=$((fails+1))
  fi

  # 3. 当前进程环境变量与 rc 一致性
  local rc_key
  rc_key="$( [ -f "${SHELL_RC}" ] && sed -n "/${MARKER_BEGIN}/,/${MARKER_END}/p" "${SHELL_RC}" \
    | sed -n 's/^export ANTHROPIC_AUTH_TOKEN="\([^"]*\)".*/\1/p' | head -1 )"
  if [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ]; then
    if [ -n "$rc_key" ] && [ "$rc_key" != "${ANTHROPIC_AUTH_TOKEN}" ]; then
      warn "当前终端的 Key 与 ${SHELL_RC} 不一致 —— 运行：source ${SHELL_RC}"
      fails=$((fails+1))
    else
      ok "当前终端环境变量已生效"
    fi
  else
    warn "当前终端未加载环境变量 —— 运行：source ${SHELL_RC}（或重开终端）"
    fails=$((fails+1))
  fi

  # 4. 网络路径 —— 用公开的 /health 探测，不需要 Key，
  #    目的是把「网络层不通」和「Key 有问题」彻底分开，避免两者都报成一个笼统的连不上
  local pxy
  pxy="$(proxy_env_summary)"
  if [ -n "$pxy" ]; then
    s_proxy="set"
    warn "检测到代理环境变量：${pxy}"
    echo "     代理软件若已退出，请求会直接被拒（ConnectionRefused）。"
    echo "     临时清除后重试：unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy"
  else
    ok "未设置代理环境变量"
  fi

  local pinned
  pinned="$(hosts_pinned_ip)"
  [ -n "$pinned" ] && info "hosts 已固定 ${API_HOST} → ${pinned}（c2a fix-ipv6 写入，撤销用 c2a fix-ipv6 undo）"

  info "检测网络路径..."
  local auto_res auto_http auto_ip
  auto_res="$(probe_health "")"
  auto_http="${auto_res%%|*}"; auto_ip="${auto_res##*|}"
  if [ "$auto_http" = "200" ]; then
    case "$auto_ip" in
      *:*) s_net="200/v6"; ok "网络可达（当前走 IPv6：${auto_ip}）" ;;
      *)   s_net="200/v4"; ok "网络可达（当前走 IPv4：${auto_ip}）" ;;
    esac
    # 小包通不等于大包通：MTU 黑洞下小请求正常、大请求被静默丢弃，
    # 表现就是「刚开始能聊，上下文一长就断」，只看上面那行会漏判
    local big
    big="$(probe_large "")"
    case "$big" in
      skip) : ;;
      000)
        s_net="${s_net}/big:fail"
        err "小数据包正常，但大数据包发不出去 —— 典型的 MTU 黑洞"
        echo "     症状：Claude Code 刚开始能用，对话一长就断（ConnectionRefused 或卡死）"
        case "$auto_ip" in
          *:*) warn "当前走的是 IPv6，运行 c2a fix-ipv6 改走 IPv4 通常可解决" ;;
          *)   warn "当前已经是 IPv4，请检查本机 VPN / 路由器的 MTU 设置" ;;
        esac
        fails=$((fails+1))
        ;;
      *) ok "大数据包传输正常（长对话不会断）" ;;
    esac
  else
    s_net="fail"
    err "无法连接 ${BASE_URL} —— 网络层不通（不是 Key 的问题）"
    fails=$((fails+1))

    # 设了代理就先做绕过代理的对照实验：一次对照就能定性，避免后面再给一堆无关的网络建议
    local culprit=0
    if [ -n "$pxy" ]; then
      local nop
      nop="$(curl --noproxy '*' -sS -o /dev/null -w '%{http_code}' --max-time 12 "${BASE_URL}/health" 2>/dev/null)" || nop="000"
      if [ "$nop" = "200" ]; then
        culprit=1
        s_proxy="culprit"; s_net="fail(proxy)"
        err "绕过代理后立刻就通了（HTTP 200）→ 问题就出在代理上，与我们的服务无关"
        echo "     解决：关掉代理软件，或在当前终端执行"
        echo "     unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy"
        echo "     若代理是常驻的，请把 ${API_HOST} 加入代理软件的直连/白名单列表"
      fi
    fi

    if [ "$culprit" = "0" ]; then
      local v4 v6 v4h v6h
      v4="$(probe_health -4)"; v6="$(probe_health -6)"
      v4h="${v4%%|*}"; v6h="${v6%%|*}"
      s_net="v4:${v4h}/v6:${v6h}"
      echo "     IPv4 通道 = ${v4h}    IPv6 通道 = ${v6h}   （200 为正常，000 为连不上）"
      if [ "$v4h" = "200" ] && [ "$v6h" != "200" ]; then
        warn "IPv4 通、IPv6 不通 —— 运行 c2a fix-ipv6 强制走 IPv4 即可解决"
      elif [ "$v4h" != "200" ] && [ "$v6h" = "200" ]; then
        warn "IPv6 通、IPv4 不通 —— 多为本机代理或防火墙拦了 IPv4 出口"
      else
        warn "两条通道都不通 —— 检查本机网络 / VPN / 防火墙；公司或校园网请换网络重试"
      fi
    fi
  fi

  # 5. Key 格式 + API 连通
  local key http
  key="$(current_key)"
  if [ -z "$key" ]; then
    err "未找到 API Key —— 运行 c2a login"
    fails=$((fails+1))
  else
    case "$key" in
      sk-c2a-*|sk-tm-*|sk-br-*) ok "API Key 格式正确（$(mask_key "$key")）" ;;
      *) warn "API Key 格式异常（$(mask_key "$key")，应以 sk-c2a- 开头）" ;;
    esac
    info "测试 API 连通..."
    http="$(probe_api "$key")"
    s_api="$http"
    case "$http" in
      200) ok "API 连通正常" ;;
      401|403) err "Key 已失效（HTTP ${http}）—— 运行 c2a login 重新授权"; fails=$((fails+1)) ;;
      429) warn "当前处于限流中（HTTP 429），配置本身正常，稍后再试" ;;
      000)
        if [ "$auto_http" = "200" ]; then
          err "网络可达但 API 请求失败 —— 可能是安全软件拦截了 POST 请求，或请求超时"
        else
          err "无法连接 ${BASE_URL}（见上面的网络路径检测）"
        fi
        fails=$((fails+1))
        ;;
      *)   warn "API 返回 HTTP ${http}，如持续异常请联系 support@code2ai.codes"; fails=$((fails+1)) ;;
    esac
  fi

  # 6. console 可达（自更新/重装依赖它）
  http="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 "${CONSOLE_URL}/c2a.version" 2>/dev/null)" || http="000"
  s_console="$http"
  if [ "$http" = "200" ]; then
    ok "控制台可达（${CONSOLE_URL}）"
  else
    warn "控制台不可达（HTTP ${http}），c2a 自更新可能失败"
  fi

  echo
  if [ "$fails" -eq 0 ]; then
    ok "自检通过，一切正常 🎉"
  else
    err "自检发现 ${fails} 个问题，请按上面提示处理；仍有疑问联系 support@code2ai.codes"
    # 编辑器里用 Claude Code 时，插件进程的环境和这个终端可能不一样，必须分开验证
    echo
    info "如果你是在 VS Code / Cursor 里用 Claude Code，请再到编辑器的「集成终端」里跑一次 c2a doctor："
    echo "     两边结果不同 = 编辑器自己的代理设置在捣乱（设置里搜 proxy，清空 Http: Proxy 后重启编辑器）"
  fi

  echo
  echo "诊断摘要（可直接发给客服）："
  printf "  c2a=%s os=%s cli=%s net=%s proxy=%s api=%s console=%s\n" \
    "$C2A_VERSION" "$(uname -s)" "$s_cli" "$s_net" "$s_proxy" "$s_api" "$s_console"
  return "$fails"
}

cmd_fix_ipv6() {
  # undo / --undo 都接受：Windows 版因 PowerShell 参数绑定只能用 undo，两边保持一致
  if [ "${1:-}" = "--undo" ] || [ "${1:-}" = "undo" ]; then
    if [ -z "$(hosts_pinned_ip)" ]; then
      info "hosts 里没有 c2a 写入的记录，无需撤销"
      return 0
    fi
    info "撤销需要管理员权限修改 ${HOSTS_FILE}"
    if sudo sed -i.code2ai.bak "/${HOSTS_MARKER}/d" "${HOSTS_FILE}"; then
      flush_dns_cache
      ok "已撤销 IPv4 固定（原文件备份为 ${HOSTS_FILE}.code2ai.bak）"
    else
      err "撤销失败 —— 可手动编辑 ${HOSTS_FILE}，删掉含 ${HOSTS_MARKER} 的那一行"
      return 1
    fi
    return 0
  fi

  info "先确认两条通道各自的状态..."
  local v4 v6 v4h v6h
  v4="$(probe_health -4)"; v6="$(probe_health -6)"
  v4h="${v4%%|*}"; v6h="${v6%%|*}"
  echo "   IPv4 通道 = ${v4h}    IPv6 通道 = ${v6h}"

  if [ "$v4h" != "200" ]; then
    err "IPv4 通道本身就不通（HTTP ${v4h}）—— 强制走 IPv4 解决不了问题"
    echo "   请先运行 c2a doctor 看完整诊断"
    return 1
  fi
  if [ "$v6h" = "200" ]; then
    warn "IPv6 当前也是通的，一般不需要固定 IPv4"
    printf "仍要继续吗？(y/N) "
    local a=""
    read -r a || true
    case "$a" in y|Y|yes|YES) ;; *) info "已取消"; return 0 ;; esac
  fi

  info "挑选可用的 IPv4 地址..."
  local ip chosen=""
  for ip in $(resolve_ipv4); do
    if curl -sS -o /dev/null --max-time 10 \
         --resolve "${API_HOST}:443:${ip}" "${BASE_URL}/health" >/dev/null 2>&1; then
      chosen="$ip"
      ok "选定 ${ip}（已验证可用）"
      break
    fi
    warn "${ip} 不可用，试下一个"
  done
  if [ -z "$chosen" ]; then
    err "没有找到可用的 IPv4 地址，无法固定"
    return 1
  fi

  case "$(uname -s)" in
    Linux)
      if grep -qi microsoft /proc/version 2>/dev/null; then
        warn "检测到 WSL：/etc/hosts 可能在每次启动时被自动重建，届时重跑本命令即可"
      fi
      ;;
  esac

  info "写入 ${HOSTS_FILE} 需要管理员权限（会先备份）"
  if sudo sh -c "sed -i.code2ai.bak '/${HOSTS_MARKER}/d' '${HOSTS_FILE}' && \
       printf '%s\t%s\t%s\n' '${chosen}' '${API_HOST}' '${HOSTS_MARKER}' >> '${HOSTS_FILE}'"; then
    flush_dns_cache
    ok "已固定 ${API_HOST} → ${chosen}"
  else
    err "写入失败 —— 可手动在 ${HOSTS_FILE} 末尾添加一行："
    echo "     ${chosen}	${API_HOST}	${HOSTS_MARKER}"
    return 1
  fi

  local after
  after="$(probe_health "")"
  if [ "${after%%|*}" = "200" ]; then
    ok "验证通过，现在连到 ${after##*|}"
  else
    warn "验证未通过（HTTP ${after%%|*}），可运行 c2a fix-ipv6 --undo 撤销"
  fi

  echo
  info "重启 Claude Code（在 VS Code / Cursor 里用的话要完全退出编辑器再打开）后生效"
  info "这是临时措施：服务商更换 IP 后会失效，届时重跑本命令即可；撤销用 c2a fix-ipv6 undo"
}

cmd_login() {
  API_KEY=""
  device_auth_flow || return 1
  write_config || return 1
  info "验证新配置..."
  local http
  http="$(probe_api "$API_KEY")"
  if [ "$http" = "200" ]; then
    ok "API 连通验证成功"
  else
    warn "API 验证未通过（HTTP=${http}），配置已写入，可稍后运行 c2a doctor 复查"
  fi
  echo
  ok "完成！新配置对新终端自动生效；当前终端请运行：source ${SHELL_RC}"
}

update_claude_cli() {
  if ! command -v claude >/dev/null 2>&1; then
    warn "Claude CLI 未安装，跳过（运行安装命令可安装）"
    return
  fi
  info "更新 Claude CLI..."
  if command -v npm >/dev/null 2>&1 && npm ls -g @anthropic-ai/claude-code >/dev/null 2>&1; then
    if npm update -g @anthropic-ai/claude-code 2>&1 | tail -3; then
      ok "Claude CLI 已更新（$(claude --version 2>/dev/null | head -1 || echo unknown)）"
      return
    fi
    warn "npm 更新失败，可稍后手动执行：npm update -g @anthropic-ai/claude-code"
  else
    # native 安装（~/.local/bin/claude）：官方 claude update 国内常被墙，提示即可
    if claude update >/dev/null 2>&1; then
      ok "Claude CLI 已更新（$(claude --version 2>/dev/null | head -1 || echo unknown)）"
    else
      warn "Claude CLI 自更新未成功（可能网络受限），可重跑安装命令覆盖安装"
    fi
  fi
}

self_update() {
  info "检查 c2a 更新..."
  local remote tmp
  remote="$(curl -fsSL --max-time 10 "${CONSOLE_URL}/c2a.version" 2>/dev/null | tr -d '[:space:]')" || remote=""
  if [ -z "$remote" ]; then
    warn "无法获取版本信息，跳过自更新"
    return
  fi
  if [ "$remote" = "$C2A_VERSION" ]; then
    ok "c2a 已是最新（${C2A_VERSION}）"
    return
  fi
  local dest="${HOME}/.local/bin/c2a"
  tmp="${HOME}/.local/bin/.c2a.new.$$"
  if ! curl -fsSL --max-time 30 "${CONSOLE_URL}/c2a.sh" -o "$tmp" 2>/dev/null; then
    rm -f "$tmp"; warn "下载新版失败，跳过自更新"; return
  fi
  if ! is_valid_shell_script "$tmp" || ! bash -n "$tmp" 2>/dev/null; then
    rm -f "$tmp"; warn "新版校验失败（内容异常），跳过自更新"; return
  fi
  chmod +x "$tmp" && mv -f "$tmp" "$dest" \
    && ok "c2a 已更新 ${C2A_VERSION} → ${remote}（下次运行生效）" \
    || { rm -f "$tmp"; warn "替换失败，跳过自更新"; }
}

cmd_update() {
  update_claude_cli
  self_update
}

cmd_uninstall() {
  echo "将执行以下操作："
  echo "  1) 从 ${SHELL_RC} 移除 code2ai 环境变量配置块（先备份）"
  echo "  2) 删除 ~/.local/bin/c2a"
  echo "  * Claude CLI 本体保留，不受影响"
  printf "确认卸载？(y/N) "
  local answer=""
  read -r answer || true
  case "$answer" in
    y|Y|yes|YES) ;;
    *) info "已取消"; return 0 ;;
  esac

  if [ -f "${SHELL_RC}" ] && grep -q "${MARKER_BEGIN}" "${SHELL_RC}" 2>/dev/null; then
    local bak="${SHELL_RC}.code2ai.bak.$(date +%s)"
    cp "${SHELL_RC}" "${bak}"
    sed -i.tmp "/${MARKER_BEGIN}/,/${MARKER_END}/d" "${SHELL_RC}"
    rm -f "${SHELL_RC}.tmp"
    ok "已移除环境变量配置（备份：${bak}）"
  fi
  rm -f "${HOME}/.local/bin/c2a"
  ok "c2a 已卸载。如需卸载 Claude CLI 本体："
  echo "     npm 安装的：  npm uninstall -g @anthropic-ai/claude-code"
  echo "     二进制安装的：rm ~/.local/bin/claude"
}

cmd_menu() {
  local key claude_ver
  key="$(current_key)"
  claude_ver="$(command -v claude >/dev/null 2>&1 && claude --version 2>/dev/null | head -1 || echo '未安装')"
  echo
  printf "   ┌─────────────────────────────────────┐\n"
  printf "   │   Code2AI 终端助手  v%-8s        │\n" "$C2A_VERSION"
  printf "   └─────────────────────────────────────┘\n"
  echo
  echo "   Claude CLI : ${claude_ver}"
  echo "   API Key    : $( [ -n "$key" ] && mask_key "$key" || echo '未配置' )"
  echo "   服务地址   : ${BASE_URL}"
  echo
  echo "   1) 环境自检（排查连不上/报错）"
  echo "   2) 重新登录授权（更换账号 / Key 失效）"
  echo "   3) 强制走 IPv4（IPv6 线路不通时用）"
  echo "   4) 检查更新（Claude CLI + c2a）"
  echo "   5) 卸载 c2a"
  echo "   0) 退出"
  echo
  printf "请选择: "
  local choice=""
  read -r choice || true
  echo
  case "$choice" in
    1) cmd_doctor ;;
    2) cmd_login ;;
    3) cmd_fix_ipv6 ;;
    4) cmd_update ;;
    5) cmd_uninstall ;;
    0|"") : ;;
    *) warn "无效选择" ;;
  esac
}

# ---------- 入口 ----------

detect_shell_rc

case "${1:-}" in
  doctor)    cmd_doctor ;;
  fix-ipv6)  cmd_fix_ipv6 "${2:-}" ;;
  login)     cmd_login ;;
  update)    cmd_update ;;
  uninstall) cmd_uninstall ;;
  version|--version|-v) echo "c2a ${C2A_VERSION}" ;;
  help|--help|-h)
    sed -n '2,11p' "$0" | sed 's/^# \{0,1\}//'
    ;;
  "")        cmd_menu ;;
  *)
    err "未知命令: $1"
    echo "可用命令：c2a [doctor|fix-ipv6|login|update|uninstall|version|help]"
    exit 1
    ;;
esac
