#!/data/data/com.termux/files/usr/bin/bash
# ═══════════════════════════════════════════════════════════════
#  adbmcp —— 一键启动 Termux MCP + ADB 设备通道
#
#  用法:
#    adbmcp              启动 (默认) —— 前台运行, Ctrl+C 停止
#    adbmcp start        同上
#    adbmcp bg           后台启动 (守护), 立即返回
#    adbmcp stop         停止所有 (MCP + 守护)
#    adbmcp status       查看状态
#    adbmcp restart      重启
#    adbmcp token        显示当前 token
#    adbmcp config       打印可供 AI 客户端直接粘贴的配置
#    adbmcp connect      手动连接设备清单里的设备
#    adbmcp lan          切换局域网模式 (0.0.0.0, 需 token, 默认开启)
#    adbmcp local        切换仅本机模式 (127.0.0.1, 免鉴权)
#    adbmcp scan [网段]  扫描网段找可连接的设备
#    adbmcp log          查看日志
#
#  设计:
#    MCP 服务绑定 0.0.0.0:8081 + Bearer token 鉴权,
#    这样同一网络下任何 AI 客户端都能直接连接:
#      http://<手机IP>:8081/mcp   (Authorization: Bearer <token>)
#    同时保留 127.0.0.1 绑定, 便于 adb forward 隧道接入。
#
#    启动时不主动连接任何设备 —— 只启动 MCP 并输出接入信息。
#    后台守护会等待设备接入, 检测到设备后才开始维持连接。
# ═══════════════════════════════════════════════════════════════

set -uo pipefail

# ── 配置 ───────────────────────────────────────────────────
BASE_DIR="$HOME/mcp-servers/adbmcp"
LOG_DIR="$BASE_DIR"
PID_FILE="$BASE_DIR/adbmcp.pid"
MCP_PORT="${MCP_PORT:-8081}"
MCP_HOST="${MCP_HOST:-0.0.0.0}"       # 0.0.0.0 = 局域网可直连
MCP_PATH="${MCP_PATH:-/mcp}"

# ══════════════════════════════════════════════════════════════
#  固定 Token
#
#  服务端(termux-native-mcp v0.15.0) 的 token 逻辑有两处, 必须同时满足:
#
#    mcp_config.native_auth_token()  →  TERMUX_NATIVE_MCP_AUTH_TOKEN
#        仅用于判断"是否要求鉴权"(require_auth) 与启动守卫
#        非 loopback 绑定时若无 token → 服务端直接 sys.exit(1) 拒绝启动
#
#    auth.authenticate(token)        →  auth.master_token()
#        实际比对用的 token。master_token() 优先级:
#          1. TERMUX_MCP_AUTH_TOKEN  环境变量   ← 必须设这个
#          2. ~/.termux-mcp/token 文件 (被删或 <16 字符会自动重新生成 → 会变)
#        另外服务端存在 auth.rotate() 接口可主动轮换 token。
#
#  → 两个环境变量都设成同一个固定值, token 永不改变。
#
#  说明: 绑定 0.0.0.0 (非 loopback) 时服务端强制要求 token,
#        完全免 token 只能绑 127.0.0.1, 那样局域网就无法直连。
# ══════════════════════════════════════════════════════════════
FIXED_TOKEN="${ADBMCP_TOKEN:-adbmcp-K60Pro-2026-fixed-8f3a9c2e}"
export TERMUX_NATIVE_MCP_AUTH_TOKEN="$FIXED_TOKEN"   # 用于 require_auth 判断
export TERMUX_MCP_AUTH_TOKEN="$FIXED_TOKEN"          # 用于实际比对

TOKEN_FILE="$HOME/.termux-mcp/token"   # 仅作参考, 实际 token 走环境变量
CONFIG_FILE="$BASE_DIR/mcp-endpoint.json"
DEVICES_FILE="$BASE_DIR/devices.txt"

mkdir -p "$BASE_DIR" "$HOME/.termux-mcp"
chmod 700 "$HOME/.termux-mcp" 2>/dev/null

cyan()  { echo -e "\033[0;36m$*\033[0m"; }
green() { echo -e "\033[0;32m$*\033[0m"; }
red()   { echo -e "\033[0;31m$*\033[0m"; }
yellow(){ echo -e "\033[1;33m$*\033[0m"; }
dim()   { echo -e "\033[2m$*\033[0m"; }
bold()  { echo -e "\033[1m$*\033[0m"; }

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_DIR/adbmcp.log"
  if [ -f "$LOG_DIR/adbmcp.log" ] && [ "$(wc -c < "$LOG_DIR/adbmcp.log")" -gt 1048576 ]; then
    mv "$LOG_DIR/adbmcp.log" "$LOG_DIR/adbmcp.log.1"
  fi
}

# ── 工具函数 ───────────────────────────────────────────────
get_phone_ip() {
  # Termux 无 ip 命令权限, /proc/net/fib_trie 也被 Android 11+ 收紧
  # → 用 python socket 探测 (不发包, 只取路由决策的本机地址)
  python3 -c "import socket;s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM);s.connect(('10.255.255.255',1));print(s.getsockname()[0])" 2>/dev/null \
    || python3 -c "import socket;s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM);s.connect(('8.8.8.8',53));print(s.getsockname()[0])" 2>/dev/null \
    || echo ""
}

ensure_token() {
  # 固定 token: 走环境变量, 服务端不会变更, 也不会因文件丢失而重新生成
  echo "$FIXED_TOKEN"
}

# 清掉可能被服务端自动生成/轮换的 token 文件, 避免与固定 token 冲突
purge_token_file() {
  if [ -f "$TOKEN_FILE" ]; then
    rm -f "$TOKEN_FILE" 2>/dev/null && log "已清除自动生成的 token 文件"
  fi
}

mcp_alive() {
  timeout 2 bash -c "exec 3<>/dev/tcp/127.0.0.1/$MCP_PORT" 2>/dev/null
}

get_mcp_pid() {
  pgrep -f "termux-native-mcp" 2>/dev/null | head -1
}

# ── 设备连接 ───────────────────────────────────────────────
# 清单里的每个目标: 状态不是 device 才执行 adb connect
device_state() {
  adb devices 2>/dev/null | awk -v t="$1" '$1==t{print $2; exit}'
}

# 已连接的设备数量 (只看 adb server 已知且状态为 device 的)
connected_device_count() {
  adb devices 2>/dev/null | awk '$2=="device"{n++} END{print n+0}'
}

connect_devices() {
  if ! command -v adb >/dev/null 2>&1; then
    yellow "  ! adb 未安装 (pkg install android-tools)"
    return 0
  fi
  [[ -f "$DEVICES_FILE" ]] || return 0

  local target state connected=0
  while read -r target _rest; do
    case "$target" in ''|\#*) continue ;; esac
    state=$(device_state "$target")
    if [ "$state" != "device" ]; then
      adb connect "$target" >/dev/null 2>&1
      sleep 1
      state=$(device_state "$target")
    fi
    if [ "$state" = "device" ]; then
      green "  ✓ $target ${_rest}"
      connected=$((connected+1))
    else
      yellow "  ✗ $target ${_rest} (${state:-未连接})"
    fi
  done < "$DEVICES_FILE"

  echo
  dim "  已连接设备: $connected 台"
}

# ── 上游补丁 ───────────────────────────────────────────────
# termux-mcp v0.15.0 的 dispatch() 未实现 MCP 规范要求的 notifications/*
# 通知方法 (notifications/cancelled 等)。客户端 (RikkaHub / Claude) 在用户
# 中断生成时会发 notifications/cancelled，服务端返回 -32601 Method not
# found，客户端把它显示成 "Error while sending message"。
#
# 补丁脚本幂等，且在每次启动时检查 —— pip 升级 termux-mcp 后会自动重打。
apply_patches() {
  local patcher="$HOME/bin/patch_cancelled.py"
  if [ ! -f "$patcher" ]; then
    patcher="$BASE_DIR/patch_cancelled.py"
  fi
  if [ -f "$patcher" ]; then
    local out
    out=$(python3 "$patcher" 2>&1)
    echo "$out" | grep -q "已为 notifications" && green "  ✓ 已应用上游补丁 (notifications/*)"
  fi
}

# 修正 CRLF 换行 —— Windows 上编辑/传输的脚本在 Termux 下会报
# "bad interpreter: /bin/bash^M"。sed -i 's/\r$//' 逐个处理。
fix_crlf() {
  local f n=0
  for f in "$0" "$BASE_DIR"/*.sh "$BASE_DIR"/*.py; do
    [ -f "$f" ] || continue
    if grep -q $'\r' "$f" 2>/dev/null; then
      sed -i 's/\r$//' "$f" 2>/dev/null && n=$((n+1))
    fi
  done
  [ "$n" -gt 0 ] && log "已修正 $n 个文件的 CRLF 换行"
}

# ── 启动 MCP ───────────────────────────────────────────────
start_mcp() {
  if mcp_alive; then
    green "  ✓ MCP 已在运行 (pid $(get_mcp_pid))"
    return 0
  fi

  pkill -f "termux-native-mcp" 2>/dev/null
  sleep 1
  purge_token_file
  fix_crlf
  apply_patches

  local token
  token=$(ensure_token)
  export TERMUX_NATIVE_MCP_PORT="$MCP_PORT"
  export TERMUX_NATIVE_MCP_HOST="$MCP_HOST"
  export TERMUX_NATIVE_MCP_PATH="$MCP_PATH"
  # 绑 127.0.0.1 时可以完全免 token (服务端只在 non-loopback 时强制要求)
  if [[ "$MCP_HOST" == "127.0.0.1" || "$MCP_HOST" == "localhost" || "$MCP_HOST" == "::1" ]]; then
    unset TERMUX_NATIVE_MCP_AUTH_TOKEN
    unset TERMUX_MCP_AUTH_TOKEN
    export TERMUX_MCP_AUTH=""
  else
    # 两个变量都要设: 前者决定"是否要求鉴权", 后者决定"比对哪个值"
    export TERMUX_NATIVE_MCP_AUTH_TOKEN="$token"
    export TERMUX_MCP_AUTH_TOKEN="$token"
    export TERMUX_MCP_AUTH=""
  fi

  log "启动 MCP  ${MCP_HOST}:${MCP_PORT}${MCP_PATH}"
  nohup termux-native-mcp --port "$MCP_PORT" --host "$MCP_HOST" \
    >> "$LOG_DIR/mcp.log" 2>&1 &
  echo $! > "$BASE_DIR/mcp.pid"

  for i in $(seq 1 20); do
    sleep 1
    if mcp_alive; then
      green "  ✓ MCP 已启动 (pid $(get_mcp_pid), ${i}s)"
      return 0
    fi
  done
  red "  ✗ MCP 启动失败，见 $LOG_DIR/mcp.log"
  return 1
}

# ── 生成 endpoint 配置 ─────────────────────────────────────
write_config() {
  local ip token
  ip=$(get_phone_ip)
  token="$FIXED_TOKEN"
  # 仅本机模式下服务端未启用鉴权, 配置里不需要 Authorization 头
  if [[ "$MCP_HOST" == "127.0.0.1" || "$MCP_HOST" == "localhost" || "$MCP_HOST" == "::1" ]]; then
    cat > "$CONFIG_FILE" <<EOF
{
  "mcpServers": {
    "adbmcp": {
      "type": "streamableHttp",
      "url": "http://127.0.0.1:${MCP_PORT}${MCP_PATH}",
      "timeout": 120000
    }
  }
}
EOF
  else
    cat > "$CONFIG_FILE" <<EOF
{
  "mcpServers": {
    "adbmcp": {
      "type": "streamableHttp",
      "url": "http://${ip}:${MCP_PORT}${MCP_PATH}",
      "timeout": 120000,
      "headers": {
        "Authorization": "Bearer ${token}"
      }
    }
  }
}
EOF
  fi
  chmod 600 "$CONFIG_FILE" 2>/dev/null
}

# ── 输出信息 ───────────────────────────────────────────────
print_banner() {
  local ip token
  ip=$(get_phone_ip)
  token="$FIXED_TOKEN"

  echo
  bold "════════════════════════════════════════════════════"
  bold "  adbmcp · Termux MCP + ADB 设备通道  已就绪"
  bold "════════════════════════════════════════════════════"
  echo
  cyan "  MCP 地址 (局域网直连):"
  echo "    http://${ip}:${MCP_PORT}${MCP_PATH}"
  echo
  cyan "  Bearer Token (固定, 不会变更):"
  echo "    ${token}"
  echo
  cyan "  AI 客户端配置 (直接粘贴):"
  cat "$CONFIG_FILE" | sed 's/^/    /'
  echo
  cyan "  备选接入 (PC 端, 走 adb 隧道, 免鉴权):"
  echo "    adb -s ${ip}:5555 forward tcp:${MCP_PORT} tcp:${MCP_PORT}"
  echo "    → http://127.0.0.1:${MCP_PORT}${MCP_PATH}"
  echo
  dim "  日志: $LOG_DIR/adbmcp.log   停止: adbmcp stop"
  echo
}

print_status() {
  echo
  bold "══ adbmcp 状态 ══"
  echo
  if mcp_alive; then
    green "  ✓ MCP 运行中  pid=$(get_mcp_pid)  ${MCP_HOST}:${MCP_PORT}"
  else
    red "  ✗ MCP 未运行"
  fi

  if [ -f "$PID_FILE" ]; then
    local p; p=$(cat "$PID_FILE" 2>/dev/null)
    if [ -n "$p" ] && kill -0 "$p" 2>/dev/null; then
      green "  ✓ adbmcp 守护中  pid=$p"
    else
      yellow "  ✗ adbmcp 守护已停止"
    fi
  else
    yellow "  ✗ adbmcp 守护已停止"
  fi

  echo
  cyan "  ── adb 设备 ──"
  command -v adb >/dev/null 2>&1 && adb devices 2>/dev/null | sed 's/^/    /' || echo "    adb 未安装"
  echo
  cyan "  ── 接入信息 ──"
  echo "    IP:   $(get_phone_ip)"
  echo "    URL:  http://$(get_phone_ip):${MCP_PORT}${MCP_PATH}"
  echo "    配置文件: $CONFIG_FILE"
  echo
}

stop_all() {
  pkill -f "termux-native-mcp" 2>/dev/null && green "✓ MCP 已停止" || echo "  (MCP 未运行)"
  if [ -f "$PID_FILE" ]; then
    local p; p=$(cat "$PID_FILE" 2>/dev/null)
    [ -n "$p" ] && kill "$p" 2>/dev/null && green "✓ 守护已停止 (pid $p)"
    rm -f "$PID_FILE"
  fi
  rm -f "$BASE_DIR/mcp.pid"
}

# ── 后台守护 ───────────────────────────────────────────────
# 等待阶段: 只观察 adb 设备数量, 不执行任何 adb connect。
# 检测到至少一个设备后才进入工作阶段, 开始维持连接。
print_usage_hints() {
  cat >> "$LOG_DIR/adbmcp.log" <<'EOF'

  ┌──────────────────────────────────────────────────────────────┐
  │  在 AI 客户端里试试这样问 (RikkaHub / Claude / Cursor 等)      │
  ├──────────────────────────────────────────────────────────────┤
  │                                                              │
  │  示例 1  查看当前连接的设备与系统信息                          │
  │    "现在 adb 能连到哪些设备？分别是什么系统？"                  │
  │                                                              │
  │  示例 2  在设备上执行命令并读取结果                            │
  │    "列出 192.168.110.39 上 /customer 目录下的文件"              │
  │                                                              │
  │  提示: 工具调用需模型支持 function calling,                   │
  │        且客户端要为本助手启用该 MCP 服务器。                     │
  └──────────────────────────────────────────────────────────────┘

EOF
}

run_guard() {
  echo $$ > "$PID_FILE"
  log "守护启动 (pid $$, host=${MCP_HOST} port=${MCP_PORT})"
  trap 'log "守护退出"; rm -f "$PID_FILE"; exit 0' TERM INT

  # ── 等待阶段: 有设备接入才开始工作 ──
  local waited=0
  while [ "$(connected_device_count)" -eq 0 ]; do
    if [ "$waited" -eq 0 ]; then
      log "等待设备接入中… (检测到设备后自动开始维护连接)"
    fi
    waited=$((waited+1))
    sleep 15
  done
  log "检测到设备接入 ($(connected_device_count) 台), 守护开始工作"
  print_usage_hints

  # ── 工作阶段: 维持连接 + 保证 MCP 存活 ──
  while true; do
    mcp_alive || start_mcp
    connect_devices >/dev/null 2>&1
    sleep 20
  done
}

# ── 模式持久化 ─────────────────────────────────────────────
MODE_FILE="$BASE_DIR/mode"

persist_mode() {
  echo "$1" > "$MODE_FILE" 2>/dev/null
  log "模式已保存: $1"
}

load_mode() {
  # 持久化模式优先于默认值
  if [ -f "$MODE_FILE" ]; then
    local m
    m=$(cat "$MODE_FILE" 2>/dev/null)
    case "$m" in
      0.0.0.0|127.0.0.1) MCP_HOST="$m"; log "加载模式: $m" ;;
    esac
  fi
}

# ── 入口 ───────────────────────────────────────────────────
load_mode

case "${1:-start}" in
  start)
    echo
    cyan "▸ 启动 adbmcp ..."
    ensure_token >/dev/null
    write_config
    start_mcp || exit 1
    print_banner
    # 守护: 已存在则复用, 否则拉起 (守护内部会等待设备接入)
    if [ -f "$PID_FILE" ]; then
      p=$(cat "$PID_FILE" 2>/dev/null)
      kill -0 "$p" 2>/dev/null || { nohup bash "$0" bg >>"$LOG_DIR/adbmcp.log" 2>&1 & sleep 1; }
    else
      nohup bash "$0" bg >>"$LOG_DIR/adbmcp.log" 2>&1 &
      sleep 1
    fi
    dim "  ✓ 后台守护已就绪 (检测到设备接入后自动开始维护连接)"
    echo
    ;;

  bg)
    run_guard
    ;;

  stop)
    stop_all
    ;;

  restart)
    stop_all; sleep 1
    bash "$0" start
    ;;

  status)
    print_status
    ;;

  token)
    echo "Token (固定, 不会变更): $FIXED_TOKEN"
    echo "来源: 环境变量 TERMUX_NATIVE_MCP_AUTH_TOKEN"
    echo "如需修改: export ADBMCP_TOKEN=<新值> 后 adbmcp restart"
    ;;

  config)
    ensure_token >/dev/null
    write_config
    cat "$CONFIG_FILE"
    ;;

  connect)
    connect_devices
    ;;

  lan)
    persist_mode "0.0.0.0"
    yellow "已切换为局域网模式 (0.0.0.0 + 固定 token)"
    bash "$0" restart
    ;;

  local)
    persist_mode "127.0.0.1"
    yellow "已切换为仅本机模式 (127.0.0.1, 免鉴权)"
    bash "$0" restart
    ;;

  scan)
    NET="${2:-192.168.110}"
    command -v python3 >/dev/null 2>&1 || { red "需要 python3"; exit 1; }
    python3 "$BASE_DIR/scan_device.py" "$NET" 2>&1 | tee "$BASE_DIR/scan_out.txt"
    ;;

  log)
    tail -n "${2:-50}" "$LOG_DIR/adbmcp.log" 2>/dev/null || echo "暂无日志"
    ;;

  *)
    echo "用法: adbmcp {start|bg|stop|restart|status|token|config|connect|lan|local|scan|log}"
    ;;
esac
