#!/usr/bin/env bash
set -Eeuo pipefail

SERVICE_NAME="${OKCLAW_SERVICE_NAME:-okclawpro-server}"
CONTAINER_NAME="${OKCLAW_CONTAINER_NAME:-}"
INSTALL_DIR="${OKCLAW_INSTALL_DIR:-/opt/okclaw/app}"
IMAGE="${OKCLAW_IMAGE:-registry.cn-shenzhen.aliyuncs.com/tycloud_share_instance/okclaw:latest}"
DOCKER_SOCKET_GID="${OKCLAW_DOCKER_SOCKET_GID:-}"
CONTAINER_PORT=9100
PORT="${OKCLAW_PORT:-${CONTAINER_PORT}}"
PORT_WAS_PROVIDED=false
[ -n "${OKCLAW_PORT:-}" ] && PORT_WAS_PROVIDED=true
WEBUI_WS_URL="${CRAFT_WEBUI_WS_URL:-}"
PUBLIC_SIGNALING_URL="${OKCLAW_PUBLIC_SIGNALING_URL:-wss://okclaw-client.dftianyi.top/signal}"
PUBLIC_SIGNALING_TOKEN="${OKCLAW_PUBLIC_SIGNALING_TOKEN:-c997a82be3d4e66b2e6d8f0a2e7a89739fcca8cde39bde429f82e198bc7ebe4d}"

usage() {
  cat <<USAGE
OK Work Docker 一键安装脚本

用法:
  curl -fsSL https://okclaw-client.dftianyi.top/install/docker/quick_start.sh | bash

参数:
  --install-dir PATH          安装目录，默认: ${INSTALL_DIR}
  --image IMAGE               Docker 镜像，默认: ${IMAGE}
  --port PORT                 宿主机端口；未指定时会提示输入，直接回车默认使用 ${CONTAINER_PORT}
  --webui-ws-url URL          浏览器 WebSocket 地址，默认根据浏览器访问地址自动生成
  --public-signaling-url URL  公网远程访问信令地址
  --public-signaling-token T  公网远程访问信令 Token
  -h, --help                  显示帮助
USAGE
}

log() { printf '\033[1;32m[OKClaw]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[OKClaw]\033[0m %s\n' "$*" >&2; }
err() { printf '\033[1;31m[OKClaw]\033[0m %s\n' "$*" >&2; }

require_root() {
  if [ "$(id -u)" -ne 0 ]; then
    err "请使用 root 用户执行，或使用 sudo: curl -fsSL ... | sudo bash"
    exit 1
  fi
}

parse_args() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --install-dir) INSTALL_DIR="${2:?missing value}"; shift 2 ;;
      --image) IMAGE="${2:?missing value}"; shift 2 ;;
      --port) PORT="${2:?missing value}"; PORT_WAS_PROVIDED=true; shift 2 ;;
      --webui-ws-url) WEBUI_WS_URL="${2:?missing value}"; shift 2 ;;
      --public-signaling-url) PUBLIC_SIGNALING_URL="${2:?missing value}"; shift 2 ;;
      --public-signaling-token) PUBLIC_SIGNALING_TOKEN="${2:?missing value}"; shift 2 ;;
      -h|--help) usage; exit 0 ;;
      *) err "未知参数: $1"; usage; exit 1 ;;
    esac
  done
}

command_exists() { command -v "$1" >/dev/null 2>&1; }

is_valid_port() {
  local port="$1"
  [ -n "$port" ] || return 1
  case "$port" in
    *[!0-9]*) return 1 ;;
  esac
  [ "$port" -ge 1 ] && [ "$port" -le 65535 ]
}

is_port_available() {
  local port="$1"

  if command_exists ss; then
    ! ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|[.:])${port}$"
    return
  fi

  if command_exists netstat; then
    ! netstat -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|[.:])${port}$"
    return
  fi

  if command_exists lsof; then
    ! lsof -iTCP:"${port}" -sTCP:LISTEN -Pn >/dev/null 2>&1
    return
  fi

  # If no port inspection tool exists, let Docker report the conflict later.
  return 0
}

find_free_port() {
  local port
  for _ in $(seq 1 200); do
    if command_exists shuf; then
      port="$(shuf -i 10000-59999 -n 1)"
    else
      port="$((10000 + RANDOM % 50000))"
    fi
    if is_port_available "$port"; then
      echo "$port"
      return 0
    fi
  done
  return 1
}

resolve_host_port() {
  local requested="$1"
  local is_manual="${2:-false}"
  local fallback_port

  if [ -z "$requested" ]; then
    if is_port_available "$CONTAINER_PORT"; then
      echo "$CONTAINER_PORT"
      return 0
    fi

    fallback_port="$(find_free_port)" || {
      err "默认端口 ${CONTAINER_PORT} 已被占用，并且未能自动找到可用端口。请使用 --port 指定端口后重试。"
      return 1
    }
    warn "默认端口 ${CONTAINER_PORT} 已被占用，已自动选择空闲端口 ${fallback_port}。"
    echo "$fallback_port"
    return 0
  fi

  if ! is_valid_port "$requested"; then
    err "端口必须是 1-65535 之间的数字，当前输入: ${requested}"
    return 1
  fi

  if ! is_port_available "$requested"; then
    if [ "$is_manual" = true ]; then
      err "端口 ${requested} 已被占用，请重新输入一个未被占用的宿主机端口。"
    else
      err "指定的端口 ${requested} 已被占用，请换一个端口后重试。"
    fi
    return 1
  fi

  echo "$requested"
}

prompt_for_port() {
  local input selected

  if [ ! -t 0 ] && [ ! -r /dev/tty ]; then
    selected="$(resolve_host_port "" false)"
    PORT="$selected"
    return 0
  fi

  while true; do
    printf '\n[OKClaw] 请输入要映射到宿主机的 Web 访问端口。\n' >/dev/tty
    printf '[OKClaw] 容器内部端口固定为 %s；直接回车默认使用宿主机端口 %s。\n' "$CONTAINER_PORT" "$CONTAINER_PORT" >/dev/tty
    printf '[OKClaw] 如果默认端口已被占用，脚本会自动选择一个未占用端口。\n' >/dev/tty
    printf '[OKClaw] 宿主机端口: ' >/dev/tty

    if ! IFS= read -r input </dev/tty; then
      input=""
    fi

    if [ -z "$input" ]; then
      selected="$(resolve_host_port "" false)"
      PORT="$selected"
      return 0
    fi

    if selected="$(resolve_host_port "$input" true)"; then
      PORT="$selected"
      return 0
    fi
  done
}

select_port() {
  local selected existing_port

  existing_port="$(get_existing_host_port)"
  if [ "$PORT_WAS_PROVIDED" = true ]; then
    if [ -n "$existing_port" ] && [ "$PORT" = "$existing_port" ]; then
      log "检测到已有安装，继续使用原宿主机端口: ${existing_port}"
      PORT="$existing_port"
      return 0
    fi

    selected="$(resolve_host_port "$PORT" false)"
    PORT="$selected"
    return 0
  fi

  if [ -n "$existing_port" ]; then
    log "检测到已有安装，继续使用原宿主机端口: ${existing_port}"
    PORT="$existing_port"
    return 0
  fi

  prompt_for_port
}

install_base_deps() {
  if command_exists apt-get; then
    apt-get update
    apt-get install -y ca-certificates curl openssl
  elif command_exists dnf; then
    dnf install -y ca-certificates curl openssl
  elif command_exists yum; then
    yum install -y ca-certificates curl openssl
  elif command_exists apk; then
    apk add --no-cache ca-certificates curl openssl
  fi
}

install_docker() {
  if command_exists docker; then
    log "Docker 已安装: $(docker --version)"
  else
    log "未检测到 Docker，开始安装 Docker"
    if command_exists apt-get; then
      apt-get update
      apt-get install -y ca-certificates curl gnupg
      install -m 0755 -d /etc/apt/keyrings
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
      chmod a+r /etc/apt/keyrings/docker.asc
      . /etc/os-release
      echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
      apt-get update
      apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    elif command_exists dnf; then
      dnf install -y dnf-plugins-core
      dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
      dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
      systemctl enable --now docker
    elif command_exists yum; then
      yum install -y yum-utils
      yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
      yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
      systemctl enable --now docker
    else
      err "当前系统无法自动安装 Docker，请先手动安装 Docker 和 docker compose 插件"
      exit 1
    fi
  fi

  systemctl enable --now docker >/dev/null 2>&1 || true

  if ! docker compose version >/dev/null 2>&1; then
    err "未检测到 docker compose v2 插件，请安装后重试"
    exit 1
  fi
}

gen_token() {
  if command_exists openssl; then
    openssl rand -hex 32
  else
    tr -dc 'a-f0-9' </dev/urandom | head -c 64
    echo
  fi
}

gen_short_suffix() {
  if command_exists openssl; then
    openssl rand -hex 4
  else
    tr -dc 'a-z0-9' </dev/urandom | head -c 8
    echo
  fi
}

detect_docker_socket_gid() {
  if [ -S /var/run/docker.sock ]; then
    stat -c '%g' /var/run/docker.sock 2>/dev/null || echo "0"
    return
  fi
  echo "0"
}

resolve_container_name() {
  local existing_container_name
  if [ -n "$CONTAINER_NAME" ]; then
    return 0
  fi

  existing_container_name="$(get_env_value OKCLAW_CONTAINER_NAME "$INSTALL_DIR/.env" 2>/dev/null || true)"
  if [ -n "$existing_container_name" ]; then
    CONTAINER_NAME="$existing_container_name"
    return 0
  fi

  CONTAINER_NAME="okclawpro-server_$(gen_short_suffix)"
}

get_env_value() {
  local key="$1" file="$2"
  [ -f "$file" ] || return 1
  grep -E "^${key}=" "$file" | tail -n1 | cut -d= -f2-
}

get_existing_host_port() {
  local env_file="$INSTALL_DIR/.env"
  local existing_port
  existing_port="$(get_env_value OKCLAWPRO_HOST_PORT "$env_file" 2>/dev/null || true)"

  if is_valid_port "$existing_port"; then
    echo "$existing_port"
  fi
}

pick_ip() {
  hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.' | grep -v '^127\.' | head -n1 || true
}

write_env() {
  local env_file="$INSTALL_DIR/.env"
  local token password ws_url docker_socket_gid
  token="$(get_env_value CRAFT_SERVER_TOKEN "$env_file" || true)"
  password="$(get_env_value CRAFT_WEBUI_PASSWORD "$env_file" || true)"
  [ -n "$token" ] || token="$(gen_token)"
  [ -n "$password" ] || password="$(gen_token | cut -c1-24)"
  docker_socket_gid="${DOCKER_SOCKET_GID:-$(detect_docker_socket_gid)}"
  [ -n "$docker_socket_gid" ] || docker_socket_gid="0"

  ws_url="$WEBUI_WS_URL"
  ws_url="$(printf '%s' "$ws_url" | tr -d '\r')"

  cat > "$env_file" <<EOF
OKCLAW_IMAGE=${IMAGE}
OKCLAW_CONTAINER_NAME=${CONTAINER_NAME}
OKCLAW_RUNTIME_KIND=docker
OKCLAW_DOCKER_SOCKET_GID=${docker_socket_gid}
OKCLAWPRO_HOST_PORT=${PORT}
CRAFT_SERVER_TOKEN=${token}
CRAFT_WEBUI_PASSWORD=${password}
CRAFT_WEBUI_WS_PORT=0
CRAFT_DEBUG=false
TZ=Asia/Shanghai
OKCLAW_PUBLIC_SIGNALING_URL=${PUBLIC_SIGNALING_URL}
OKCLAW_PUBLIC_SIGNALING_TOKEN=${PUBLIC_SIGNALING_TOKEN}
EOF
  if [ -n "$ws_url" ]; then
    printf 'CRAFT_WEBUI_WS_URL=%s\n' "$ws_url" >> "$env_file"
  fi
  chmod 600 "$env_file"
}

write_compose() {
  cat > "$INSTALL_DIR/docker-compose.yml" <<'EOF'
services:
  okclawpro-server:
    image: ${OKCLAW_IMAGE}
    container_name: ${OKCLAW_CONTAINER_NAME}
    restart: unless-stopped
    init: true
    group_add:
      - "${OKCLAW_DOCKER_SOCKET_GID:-0}"
    ports:
      - "${OKCLAWPRO_HOST_PORT:-9100}:9100"
    environment:
      CRAFT_SERVER_TOKEN: "${CRAFT_SERVER_TOKEN:?Set CRAFT_SERVER_TOKEN in .env}"
      CRAFT_WEBUI_PASSWORD: "${CRAFT_WEBUI_PASSWORD:-}"
      CRAFT_WEBUI_WS_URL: "${CRAFT_WEBUI_WS_URL:-}"
      CRAFT_WEBUI_WS_PORT: "${CRAFT_WEBUI_WS_PORT:-0}"
      CRAFT_DEBUG: "${CRAFT_DEBUG:-false}"
      TZ: "${TZ:-Asia/Shanghai}"
      OKCLAW_PUBLIC_SIGNALING_URL: "${OKCLAW_PUBLIC_SIGNALING_URL:-}"
      OKCLAW_PUBLIC_SIGNALING_TOKEN: "${OKCLAW_PUBLIC_SIGNALING_TOKEN:-}"
      OKCLAW_RUNTIME_KIND: docker
      OKCLAW_IMAGE: "${OKCLAW_IMAGE:-registry.cn-shenzhen.aliyuncs.com/tycloud_share_instance/okclaw:latest}"
      OKCLAW_CONTAINER_NAME: "${OKCLAW_CONTAINER_NAME:-}"
    volumes:
      - okclawpro-data:/home/okclawpro/.okclawpro
      - /var/run/docker.sock:/var/run/docker.sock
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9100/health >/dev/null"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3

volumes:
  okclawpro-data:
EOF
}

wait_health() {
  local url="http://127.0.0.1:${PORT}/health"
  for _ in $(seq 1 45); do
    if curl -fsS "$url" >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done
  return 1
}

print_result() {
  local ip public_url token password ws_url
  ip="$(pick_ip)"
  [ -n "$ip" ] || ip="127.0.0.1"
  public_url="请使用服务器实际公网 IP 或域名加端口访问，例如: http://<公网IP>:${PORT}"
  token="$(get_env_value CRAFT_SERVER_TOKEN "$INSTALL_DIR/.env")"
  password="$(get_env_value CRAFT_WEBUI_PASSWORD "$INSTALL_DIR/.env")"
  ws_url="$(get_env_value CRAFT_WEBUI_WS_URL "$INSTALL_DIR/.env" || true)"
  [ -n "$ws_url" ] || ws_url="自动根据浏览器访问地址生成（会使用你打开页面时的域名/IP 和端口）"

  cat <<EOF

============================================================
OK Work Docker 版已安装并启动
============================================================

内网访问地址:
  http://${ip}:${PORT}
  http://127.0.0.1:${PORT}

公网访问地址:
  ${public_url}

WebSocket 地址:
  ${ws_url}

Web 访问 Token:
  ${token}

Web 登录密码(如界面要求):
  ${password}

安装目录:
  ${INSTALL_DIR}

镜像:
  ${IMAGE}

服务管理:
  cd ${INSTALL_DIR} && docker compose ps
  cd ${INSTALL_DIR} && docker compose logs -f
  cd ${INSTALL_DIR} && docker compose restart

EOF
}

main() {
  parse_args "$@"
  require_root
  install_base_deps
  install_docker
  select_port
  resolve_container_name

  mkdir -p "$INSTALL_DIR"
  write_env
  write_compose

  log "拉取镜像: ${IMAGE}"
  docker pull "$IMAGE"

  log "启动 OK Work Docker 服务"
  cd "$INSTALL_DIR"
  docker compose up -d --force-recreate

  if wait_health; then
    log "服务健康检查通过"
  else
    warn "服务健康检查未在预期时间内通过，请执行 cd ${INSTALL_DIR} && docker compose logs -f 查看日志"
  fi
  print_result
}

if [ "${OKCLAW_QUICK_START_SOURCE_ONLY:-false}" != true ]; then
  main "$@"
fi
