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

SCRIPT_VERSION="1.0.0"
DEFAULT_IMAGE="docker.io/library/couchdb:3.4.3"
DEFAULT_PORT="5984"
DEFAULT_DATABASE="modou_livesync"
DEFAULT_CONTAINER="modou-couchdb"
INSTALL_MARKER="# Generated by Modow CouchDB Installer"
NON_INTERACTIVE=0
DOCTOR_ONLY=0
INSTALL_DIR="${MODOU_INSTALL_DIR:-$HOME/modou-couchdb}"
RUNTIME=""
COMPOSE_MODE=""
BASE_URL=""
DATABASE_CREATED=0

info() { printf '[INFO] %s\n' "$*"; }
ok() { printf '[OK] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
fail() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }

on_error() {
  local line="$1"
  printf '[ERROR] 安装未完成（脚本第 %s 行）。请保留上面的错误信息后重试。\n' "$line" >&2
}
trap 'on_error "$LINENO"' ERR

usage() {
  cat <<'EOF'
墨斗 CouchDB 安装器 1.0.0

用法：
  bash install.sh
  bash install.sh --doctor
  bash install.sh --non-interactive --install-dir /srv/modou-couchdb

无人值守模式通过环境变量传入：
  MODOU_ADMIN_USER             CouchDB 管理员用户名
  MODOU_ADMIN_PASSWORD         CouchDB 管理员密码
  MODOU_SYNC_USER              日常同步用户名
  MODOU_SYNC_PASSWORD          日常同步密码
  MODOU_DATABASE               数据库名
  MODOU_PORT                   监听端口
  MODOU_BIND_ADDRESS           127.0.0.1 或 0.0.0.0
  MODOU_PUBLIC_URL             客户端实际访问地址（可选）
  MODOU_ALLOW_HTTP             yes 或 no
  MODOU_INITIALIZE_LIVESYNC    yes 或 no
  MODOU_COUCHDB_IMAGE          镜像，默认 couchdb:3.4.3
  MODOU_CONTAINER_NAME         容器名，默认 modou-couchdb
EOF
}

while (($# > 0)); do
  case "$1" in
    --non-interactive) NON_INTERACTIVE=1 ;;
    --doctor) DOCTOR_ONLY=1 ;;
    --install-dir)
      shift
      [[ $# -gt 0 ]] || fail '--install-dir 缺少目录。'
      INSTALL_DIR="$1"
      ;;
    --version) printf '%s\n' "$SCRIPT_VERSION"; exit 0 ;;
    -h|--help) usage; exit 0 ;;
    *) fail "未知参数：$1。使用 --help 查看说明。" ;;
  esac
  shift
done

require_command() {
  command -v "$1" >/dev/null 2>&1 || fail "缺少命令 $1。$2"
}

version_major() {
  printf '%s' "$1" | sed -E 's/^[^0-9]*([0-9]+).*/\1/'
}

detect_platform() {
  local os arch
  os="$(uname -s 2>/dev/null || true)"
  arch="$(uname -m 2>/dev/null || true)"
  case "$os" in
    Darwin) info "系统：macOS (${arch})" ;;
    Linux)
      if grep -qi microsoft /proc/version 2>/dev/null; then
        info "系统：Windows WSL (${arch})"
      else
        info "系统：Linux (${arch})"
      fi
      ;;
    *) fail "不支持的系统：${os:-unknown}。Windows 请运行 install.ps1。" ;;
  esac
  case "$arch" in
    x86_64|amd64|aarch64|arm64) ;;
    *) fail "当前 CPU 架构 $arch 没有经过验证。支持 x86_64/amd64/arm64。" ;;
  esac
}

detect_runtime() {
  if command -v podman >/dev/null 2>&1 && docker --version 2>/dev/null | grep -qi podman &&
    podman info >/dev/null 2>&1; then
    RUNTIME="podman"
  elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
    RUNTIME="docker"
  elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then
    RUNTIME="podman"
  elif command -v docker >/dev/null 2>&1; then
    fail '已找到 Docker，但服务没有运行。请启动 Docker Desktop 或 Docker Engine 后重试。'
  elif command -v podman >/dev/null 2>&1; then
    fail '已找到 Podman，但服务没有运行。请启动 Podman machine/service 后重试。'
  else
    fail '没有找到 Docker/Podman。macOS、Windows 请先安装并启动 Docker Desktop；Linux/NAS 请安装 Docker Engine。'
  fi

  local raw major
  raw="$($RUNTIME version --format '{{.Server.Version}}' 2>/dev/null || $RUNTIME --version)"
  major="$(version_major "$raw")"
  [[ "$major" =~ ^[0-9]+$ ]] || fail "无法识别 $RUNTIME 版本：$raw"
  if [[ "$RUNTIME" == "docker" && "$major" -lt 20 ]]; then
    fail "Docker 版本过低（${raw}），请升级到 20.10 或更高版本。"
  fi

  if "$RUNTIME" compose version >/dev/null 2>&1; then
    COMPOSE_MODE="plugin"
  elif [[ "$RUNTIME" == "docker" ]] && command -v docker-compose >/dev/null 2>&1; then
    COMPOSE_MODE="standalone"
  else
    COMPOSE_MODE="direct"
    warn '没有找到 Docker Compose，将使用等价的容器命令启动；仍会生成 docker-compose.yml。'
  fi
  ok "容器环境可用：$($RUNTIME --version | head -1)"
}

compose() {
  case "$COMPOSE_MODE" in
    plugin) "$RUNTIME" compose "$@" ;;
    standalone) docker-compose "$@" ;;
    *) return 127 ;;
  esac
}

named_value() {
  local name="$1"
  eval "printf '%s' \"\${$name-}\""
}

prompt_value() {
  local variable="$1" label="$2" default_value="$3" secret="${4:-no}" current
  current="$(named_value "$variable")"
  if [[ -n "$current" ]]; then
    return
  fi
  if [[ "$NON_INTERACTIVE" -eq 1 ]]; then
    [[ -n "$default_value" ]] || fail "无人值守模式缺少 ${variable}。"
    printf -v "$variable" '%s' "$default_value"
    return
  fi
  if [[ "$secret" == "yes" ]]; then
    while [[ -z "$(named_value "$variable")" ]]; do
      read -r -s -p "${label}：" current
      printf '\n'
      [[ -n "$current" ]] || warn '不能为空。'
      printf -v "$variable" '%s' "$current"
    done
  else
    read -r -p "${label}${default_value:+ [$default_value]}：" current
    printf -v "$variable" '%s' "${current:-$default_value}"
  fi
}

prompt_yes_no() {
  local variable="$1" label="$2" default_value="$3" answer
  if [[ -n "$(named_value "$variable")" ]]; then
    answer="$(named_value "$variable")"
  elif [[ "$NON_INTERACTIVE" -eq 1 ]]; then
    answer="$default_value"
  else
    read -r -p "$label [$([[ "$default_value" == "yes" ]] && printf 'Y/n' || printf 'y/N')]：" answer
    answer="${answer:-$default_value}"
  fi
  answer="$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]')"
  case "$answer" in
    y|yes|1|true|是) printf -v "$variable" 'yes' ;;
    n|no|0|false|否) printf -v "$variable" 'no' ;;
    *) fail "${label} 只能选择 yes 或 no。" ;;
  esac
}

validate_plain_value() {
  local label="$1" value="$2"
  [[ -n "$value" ]] || fail "${label} 不能为空。"
  [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || fail "${label} 不能包含换行。"
  [[ "$value" != *"'"* ]] || fail "${label} 暂不支持英文单引号，请换一个值。"
}

validate_inputs() {
  [[ "$MODOU_ADMIN_USER" =~ ^[A-Za-z0-9][A-Za-z0-9._@-]{2,63}$ ]] ||
    fail '管理员用户名需为 3-64 位，只能使用字母、数字、点、下划线、@ 和连字符。'
  [[ "$MODOU_SYNC_USER" =~ ^[A-Za-z0-9][A-Za-z0-9._@-]{2,63}$ ]] ||
    fail '同步用户名需为 3-64 位，只能使用字母、数字、点、下划线、@ 和连字符。'
  [[ "$MODOU_ADMIN_USER" != "$MODOU_SYNC_USER" ]] || fail '管理员账号和同步账号不能相同。'
  [[ ${#MODOU_ADMIN_PASSWORD} -ge 10 ]] || fail '管理员密码至少 10 位。'
  [[ ${#MODOU_SYNC_PASSWORD} -ge 8 ]] || fail '同步密码至少 8 位。'
  validate_plain_value '管理员密码' "$MODOU_ADMIN_PASSWORD"
  validate_plain_value '同步密码' "$MODOU_SYNC_PASSWORD"
  [[ "$MODOU_DATABASE" =~ ^[a-z][a-z0-9_+()$-]{2,63}$ ]] ||
    fail '数据库名需为 3-64 位，以小写字母开头，只能使用小写字母、数字、_、+、-、(、)。'
  [[ "$MODOU_PORT" =~ ^[0-9]+$ ]] || fail '端口必须是数字。'
  ((MODOU_PORT >= 1024 && MODOU_PORT <= 65535)) || fail '端口需在 1024-65535 之间。'
  [[ "$MODOU_BIND_ADDRESS" == '127.0.0.1' || "$MODOU_BIND_ADDRESS" == '0.0.0.0' ]] ||
    fail '监听地址只能是 127.0.0.1 或 0.0.0.0。'
  [[ "$MODOU_COUCHDB_IMAGE" =~ ^((docker\.io/)?(library/)?couchdb|(docker\.io/)?apache/couchdb):3([.][0-9]+){0,2}$ ]] ||
    fail '镜像必须是 CouchDB 3.x 的明确版本，例如 couchdb:3.4.3。'
  [[ "$MODOU_CONTAINER_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{2,63}$ ]] || fail '容器名格式不正确。'
  if [[ -n "$MODOU_PUBLIC_URL" ]]; then
    [[ "$MODOU_PUBLIC_URL" =~ ^https?://[^[:space:]/]+(:[0-9]+)?(/[^[:space:]]*)?$ ]] ||
      fail '客户端访问地址需是完整的 http:// 或 https:// 地址。'
    [[ "$MODOU_PUBLIC_URL" != *'@'* && "$MODOU_PUBLIC_URL" != *'?'* && "$MODOU_PUBLIC_URL" != *'#'* ]] ||
      fail '客户端访问地址不能包含账号、查询参数或锚点。'
  fi
  if [[ "$MODOU_ALLOW_HTTP" == 'no' ]]; then
    [[ "$MODOU_PUBLIC_URL" == https://* ]] || fail '不允许 HTTP 时，必须填写已配置好的 HTTPS 客户端访问地址。'
    [[ "$MODOU_BIND_ADDRESS" == '127.0.0.1' ]] || fail '使用 HTTPS 反向代理时，CouchDB 建议只监听 127.0.0.1。'
  fi
}

load_existing_config() {
  local env_file="$INSTALL_DIR/.env"
  [[ -f "$env_file" && -f "$INSTALL_DIR/docker-compose.yml" ]] || return 1
  grep -Fq "$INSTALL_MARKER" "$INSTALL_DIR/docker-compose.yml" ||
    fail "$INSTALL_DIR 已有非墨斗生成的 docker-compose.yml，已停止以免覆盖。"
  local key raw value
  while IFS='=' read -r key raw; do
    case "$key" in
      MODOU_COUCHDB_IMAGE|MODOU_CONTAINER_NAME|MODOU_ADMIN_USER|MODOU_ADMIN_PASSWORD|MODOU_SYNC_USER|\
      MODOU_SYNC_PASSWORD|MODOU_DATABASE|MODOU_PORT|MODOU_BIND_ADDRESS|MODOU_PUBLIC_URL|\
      MODOU_ALLOW_HTTP|MODOU_INITIALIZE_LIVESYNC)
        [[ "$raw" == "'"*"'" ]] || fail "已有配置中 $key 格式不正确。"
        value="${raw#\'}"
        value="${value%\'}"
        printf -v "$key" '%s' "$value"
        export "$key"
        ;;
    esac
  done <"$env_file"
  [[ -n "${MODOU_ALLOW_HTTP:-}" ]] || MODOU_ALLOW_HTTP='no'
  [[ -n "${MODOU_INITIALIZE_LIVESYNC:-}" ]] || MODOU_INITIALIZE_LIVESYNC='yes'
  ok "检测到已有墨斗 CouchDB 配置，将复用并检查，不会删除数据。"
  return 0
}

collect_inputs() {
  if load_existing_config; then
    return
  fi
  MODOU_COUCHDB_IMAGE="${MODOU_COUCHDB_IMAGE:-$DEFAULT_IMAGE}"
  MODOU_CONTAINER_NAME="${MODOU_CONTAINER_NAME:-$DEFAULT_CONTAINER}"
  prompt_value MODOU_ADMIN_USER 'CouchDB 管理员用户名' 'modouadmin'
  prompt_value MODOU_ADMIN_PASSWORD 'CouchDB 管理员密码（至少 10 位）' '' yes
  prompt_value MODOU_SYNC_USER '墨斗日常同步用户名' 'modousync'
  prompt_value MODOU_SYNC_PASSWORD '墨斗日常同步密码（至少 8 位）' '' yes
  prompt_value MODOU_DATABASE '同步数据库名' "$DEFAULT_DATABASE"
  prompt_value MODOU_PORT '监听端口' "$DEFAULT_PORT"
  prompt_value MODOU_BIND_ADDRESS '监听地址（127.0.0.1 仅本机；0.0.0.0 局域网）' '127.0.0.1'
  prompt_yes_no MODOU_ALLOW_HTTP '是否允许客户端使用 HTTP（仅受信任局域网）' 'no'
  if [[ "$MODOU_ALLOW_HTTP" == 'yes' ]]; then
    prompt_value MODOU_PUBLIC_URL '客户端实际访问地址（可留空自动生成）' ''
  else
    prompt_value MODOU_PUBLIC_URL '已配置好的 HTTPS 访问地址' ''
  fi
  prompt_yes_no MODOU_INITIALIZE_LIVESYNC '是否初始化为 LiveSync 兼容库' 'yes'
  validate_inputs
}

port_is_busy() {
  if command -v lsof >/dev/null 2>&1; then
    lsof -nP -iTCP:"$MODOU_PORT" -sTCP:LISTEN >/dev/null 2>&1
  elif command -v ss >/dev/null 2>&1; then
    ss -ltnH | awk '{print $4}' | grep -Eq "(^|:)$MODOU_PORT$"
  else
    return 1
  fi
}

container_exists() {
  "$RUNTIME" container inspect "$MODOU_CONTAINER_NAME" >/dev/null 2>&1
}

verify_existing_container() {
  container_exists || return 1
  local marker
  marker="$($RUNTIME inspect --format '{{ index .Config.Labels "com.termlyhos.modou.couchdb" }}' "$MODOU_CONTAINER_NAME" 2>/dev/null || true)"
  [[ "$marker" == '1' ]] || fail "容器名 $MODOU_CONTAINER_NAME 已被其他容器占用，请更换 MODOU_CONTAINER_NAME。"
  return 0
}

write_files() {
  umask 077
  mkdir -p "$INSTALL_DIR"
  cat >"$INSTALL_DIR/.env" <<EOF
MODOU_COUCHDB_IMAGE='$MODOU_COUCHDB_IMAGE'
MODOU_CONTAINER_NAME='$MODOU_CONTAINER_NAME'
MODOU_ADMIN_USER='$MODOU_ADMIN_USER'
MODOU_ADMIN_PASSWORD='$MODOU_ADMIN_PASSWORD'
MODOU_SYNC_USER='$MODOU_SYNC_USER'
MODOU_SYNC_PASSWORD='$MODOU_SYNC_PASSWORD'
MODOU_DATABASE='$MODOU_DATABASE'
MODOU_PORT='$MODOU_PORT'
MODOU_BIND_ADDRESS='$MODOU_BIND_ADDRESS'
MODOU_PUBLIC_URL='$MODOU_PUBLIC_URL'
MODOU_ALLOW_HTTP='$MODOU_ALLOW_HTTP'
MODOU_INITIALIZE_LIVESYNC='$MODOU_INITIALIZE_LIVESYNC'
MODOU_DATA_VOLUME='$MODOU_CONTAINER_NAME-data'
COUCHDB_USER='$MODOU_ADMIN_USER'
COUCHDB_PASSWORD='$MODOU_ADMIN_PASSWORD'
EOF
  chmod 600 "$INSTALL_DIR/.env"

  cat >"$INSTALL_DIR/modou.ini" <<'EOF'
[couchdb]
single_node = true

[chttpd]
bind_address = 0.0.0.0
require_valid_user = true
enable_cors = true

[httpd]
enable_cors = true

[cors]
credentials = true
origins = app://obsidian.md, capacitor://localhost, http://localhost
headers = accept, authorization, content-type, origin, referer
methods = GET, PUT, POST, HEAD, DELETE, OPTIONS
EOF

  cat >"$INSTALL_DIR/docker-compose.yml" <<EOF
$INSTALL_MARKER
services:
  couchdb:
    image: \${MODOU_COUCHDB_IMAGE}
    container_name: \${MODOU_CONTAINER_NAME}
    restart: unless-stopped
    ports:
      - "\${MODOU_BIND_ADDRESS}:\${MODOU_PORT}:5984"
    environment:
      COUCHDB_USER: \${MODOU_ADMIN_USER}
      COUCHDB_PASSWORD: \${MODOU_ADMIN_PASSWORD}
    volumes:
      - modou-couchdb-data:/opt/couchdb/data
      - ./modou.ini:/opt/couchdb/etc/local.d/modou.ini
    labels:
      com.termlyhos.modou.couchdb: "1"
      com.termlyhos.modou.installer-version: "$SCRIPT_VERSION"

volumes:
  modou-couchdb-data:
    name: \${MODOU_DATA_VOLUME}
EOF
  chmod 600 "$INSTALL_DIR/docker-compose.yml"
  chmod 644 "$INSTALL_DIR/modou.ini"
}

start_couchdb() {
  cd "$INSTALL_DIR"
  if verify_existing_container; then
    info '已有受管容器，正在启动并检查。'
    "$RUNTIME" start "$MODOU_CONTAINER_NAME" >/dev/null 2>&1 || true
    return
  fi
  if port_is_busy; then
    fail "端口 $MODOU_PORT 已被占用。请换端口，或停止占用该端口的服务。"
  fi
  local free_kb
  free_kb="$(df -Pk "$INSTALL_DIR" | awk 'NR == 2 {print $4}')"
  if [[ "$free_kb" =~ ^[0-9]+$ && "$free_kb" -lt 1048576 ]]; then
    fail '可用磁盘空间不足 1GB，请清理后重试。'
  fi
  info "下载并检查镜像 ${MODOU_COUCHDB_IMAGE}。网络较慢时可能需要几分钟。"
  "$RUNTIME" pull "$MODOU_COUCHDB_IMAGE" || fail '镜像下载失败，请检查网络、代理或 Docker Hub 访问。'
  if [[ "$COMPOSE_MODE" != 'direct' ]]; then
    compose config >/dev/null || fail 'docker-compose.yml 校验失败。'
    compose up -d || fail 'CouchDB 容器启动失败，请运行 docker compose logs 查看原因。'
  else
    "$RUNTIME" volume create "$MODOU_CONTAINER_NAME-data" >/dev/null
    "$RUNTIME" run -d \
      --name "$MODOU_CONTAINER_NAME" \
      --restart unless-stopped \
      --label com.termlyhos.modou.couchdb=1 \
      --label "com.termlyhos.modou.installer-version=$SCRIPT_VERSION" \
      -p "$MODOU_BIND_ADDRESS:$MODOU_PORT:5984" \
      -e COUCHDB_USER -e COUCHDB_PASSWORD \
      -v "$MODOU_CONTAINER_NAME-data:/opt/couchdb/data" \
      -v "$INSTALL_DIR/modou.ini:/opt/couchdb/etc/local.d/modou.ini:Z" \
      "$MODOU_COUCHDB_IMAGE" >/dev/null || fail 'CouchDB 容器启动失败。'
  fi
}

curl_status() {
  local method="$1" url="$2" user="$3" password="$4" body="${5:-}" output="${6:-/dev/null}"
  local args=(--silent --show-error --connect-timeout 5 --max-time 20 --output "$output" --write-out '%{http_code}'
    --request "$method" --user "$user:$password" --header 'Accept: application/json')
  if [[ -n "$body" ]]; then
    args+=(--header 'Content-Type: application/json' --data "$body")
  fi
  curl "${args[@]}" "$url" 2>/dev/null || printf '000'
}

wait_for_couchdb() {
  BASE_URL="http://127.0.0.1:$MODOU_PORT"
  local attempt status
  for attempt in $(seq 1 60); do
    status="$(curl_status GET "$BASE_URL/_up" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD")"
    if [[ "$status" == '200' ]]; then
      ok 'CouchDB 已启动。'
      return
    fi
    if ! "$RUNTIME" inspect "$MODOU_CONTAINER_NAME" >/dev/null 2>&1; then
      fail 'CouchDB 容器意外退出。'
    fi
    sleep 1
  done
  "$RUNTIME" logs --tail 40 "$MODOU_CONTAINER_NAME" >&2 || true
  fail 'CouchDB 在 60 秒内没有就绪。常见原因是端口冲突、磁盘不足或旧数据权限异常。'
}

verify_server_version() {
  local temp status
  temp="$(mktemp)"
  status="$(curl_status GET "$BASE_URL/" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD" '' "$temp")"
  [[ "$status" == '200' ]] || { rm -f "$temp"; fail "无法读取 CouchDB 版本（HTTP ${status}）。"; }
  if ! grep -Eq '"version"[[:space:]]*:[[:space:]]*"3[.]' "$temp"; then
    rm -f "$temp"
    fail '只支持 CouchDB 3.x，检测到其他版本。已有数据不会被修改。'
  fi
  rm -f "$temp"
  ok 'CouchDB 3.x 版本检查通过。'
}

json_escape() {
  local value="$1"
  value="${value//\\/\\\\}"
  value="${value//\"/\\\"}"
  printf '%s' "$value"
}

random_base64() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 32 | tr -d '\r\n'
  else
    dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\r\n'
  fi
}

random_letters() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 32 | tr '0-9+/' 'abcdefghijk' | tr -d '=\r\n' | cut -c1-30
  else
    random_base64 | tr '0-9+/' 'abcdefghijk' | tr -d '=\r\n' | cut -c1-30
  fi
}

ensure_database_and_user() {
  local status user_id user_body security_body
  status="$(curl_status GET "$BASE_URL/$MODOU_DATABASE" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD")"
  case "$status" in
    200) info "数据库 $MODOU_DATABASE 已存在，不会覆盖或清空。" ;;
    404)
      status="$(curl_status PUT "$BASE_URL/$MODOU_DATABASE" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD" '{}')"
      [[ "$status" == '201' || "$status" == '202' ]] || fail "创建数据库失败（HTTP ${status}）。"
      DATABASE_CREATED=1
      ok "数据库 $MODOU_DATABASE 已创建。"
      ;;
    401) fail '管理员用户名或密码不正确。已有数据目录不会因修改 .env 自动更换管理员密码。' ;;
    403) fail '当前账号没有创建或读取数据库的权限。' ;;
    000) fail '无法连接 CouchDB，请检查容器日志和本机防火墙。' ;;
    *) fail "检查数据库失败（HTTP ${status}）。" ;;
  esac

  user_id="org.couchdb.user:$MODOU_SYNC_USER"
  status="$(curl_status GET "$BASE_URL/_users/$user_id" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD")"
  if [[ "$status" == '404' ]]; then
    user_body="{\"_id\":\"$user_id\",\"name\":\"$(json_escape "$MODOU_SYNC_USER")\",\"password\":\"$(json_escape "$MODOU_SYNC_PASSWORD")\",\"roles\":[],\"type\":\"user\"}"
    status="$(curl_status PUT "$BASE_URL/_users/$user_id" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD" "$user_body")"
    [[ "$status" == '201' || "$status" == '202' ]] || fail "创建同步用户失败（HTTP ${status}）。"
    ok "同步用户 $MODOU_SYNC_USER 已创建。"
  elif [[ "$status" == '200' ]]; then
    info "同步用户 $MODOU_SYNC_USER 已存在，不会自动修改密码。"
  else
    fail "检查同步用户失败（HTTP ${status}）。"
  fi

  status="$(curl_status GET "$BASE_URL/$MODOU_DATABASE" "$MODOU_SYNC_USER" "$MODOU_SYNC_PASSWORD")"
  if [[ "$status" == '401' && "$DATABASE_CREATED" -eq 0 ]]; then
    fail '同步用户已存在，但输入的同步密码不正确。为避免意外改密，安装器已停止。'
  fi

  if [[ "$DATABASE_CREATED" -eq 1 ]]; then
    security_body="{\"admins\":{\"names\":[],\"roles\":[]},\"members\":{\"names\":[\"$(json_escape "$MODOU_SYNC_USER")\"],\"roles\":[]}}"
    status="$(curl_status PUT "$BASE_URL/$MODOU_DATABASE/_security" "$MODOU_ADMIN_USER" "$MODOU_ADMIN_PASSWORD" "$security_body")"
    [[ "$status" == '200' || "$status" == '201' ]] || fail "设置数据库权限失败（HTTP ${status}）。"
  elif [[ "$status" != '200' ]]; then
    fail '数据库已存在，但当前同步用户没有访问权限。安装器不会覆盖已有库的权限，请在 CouchDB 中授权或换新库名。'
  fi

  status="$(curl_status GET "$BASE_URL/$MODOU_DATABASE" "$MODOU_SYNC_USER" "$MODOU_SYNC_PASSWORD")"
  [[ "$status" == '200' ]] || fail "同步用户访问验证失败（HTTP ${status}）。"
  ok '同步账号已限制并验证到目标数据库。'
}

ensure_document() {
  local path="$1" body="$2" expected_pattern="$3" label="$4" status temp
  temp="$(mktemp)"
  status="$(curl_status GET "$BASE_URL/$MODOU_DATABASE/$path" "$MODOU_SYNC_USER" "$MODOU_SYNC_PASSWORD" '' "$temp")"
  if [[ "$status" == '404' ]]; then
    status="$(curl_status PUT "$BASE_URL/$MODOU_DATABASE/$path" "$MODOU_SYNC_USER" "$MODOU_SYNC_PASSWORD" "$body")"
    rm -f "$temp"
    [[ "$status" == '201' || "$status" == '202' ]] || fail "写入 ${label} 失败（HTTP ${status}）。"
    ok "${label} 已写入。"
    return
  fi
  [[ "$status" == '200' ]] || { rm -f "$temp"; fail "读取 ${label} 失败（HTTP ${status}）。"; }
  grep -Eq "$expected_pattern" "$temp" || { rm -f "$temp"; fail "${label} 已存在但版本不兼容，安装器不会覆盖。请换一个空数据库。"; }
  rm -f "$temp"
  info "${label} 已存在且兼容。"
}

initialize_livesync() {
  [[ "$MODOU_INITIALIZE_LIVESYNC" == 'yes' ]] || { warn '已跳过 LiveSync 初始化；稍后可在墨斗中初始化。'; return; }
  local salt syncinfo
  salt="$(random_base64)"
  syncinfo="$(random_letters)"
  ensure_document 'obsydian_livesync_version' \
    '{"_id":"obsydian_livesync_version","version":12,"type":"versioninfo"}' \
    '"version"[[:space:]]*:[[:space:]]*12' 'LiveSync 数据库版本'
  ensure_document '_local/obsidian_livesync_sync_parameters' \
    "{\"_id\":\"_local/obsidian_livesync_sync_parameters\",\"type\":\"sync-parameters\",\"protocolVersion\":2,\"pbkdf2salt\":\"$salt\"}" \
    '"protocolVersion"[[:space:]]*:[[:space:]]*2' 'LiveSync 同步参数'
  ensure_document 'syncinfo' \
    "{\"_id\":\"syncinfo\",\"type\":\"syncinfo\",\"data\":\"$syncinfo\"}" \
    '"type"[[:space:]]*:[[:space:]]*"syncinfo"' 'LiveSync 同步标识'
}

infer_public_url() {
  if [[ -n "$MODOU_PUBLIC_URL" ]]; then
    printf '%s' "${MODOU_PUBLIC_URL%/}"
    return
  fi
  local host='127.0.0.1'
  if [[ "$MODOU_BIND_ADDRESS" == '0.0.0.0' ]]; then
    if command -v hostname >/dev/null 2>&1; then
      host="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
    fi
    if [[ -z "$host" || "$host" == '127.0.0.1' ]]; then
      host='服务器IP'
    fi
  fi
  printf 'http://%s:%s' "$host" "$MODOU_PORT"
}

print_result() {
  local public_url
  public_url="$(infer_public_url)"
  printf '\n========== 安装完成 ==========\n'
  printf '服务地址：%s\n' "$public_url"
  printf '数据库：%s\n' "$MODOU_DATABASE"
  printf '用户名：%s\n' "$MODOU_SYNC_USER"
  printf '密码：使用安装时填写的同步密码\n'
  printf '管理页面：%s/_utils\n' "$public_url"
  printf '配置目录：%s\n' "$INSTALL_DIR"
  printf '==============================\n'
  if [[ "$MODOU_ALLOW_HTTP" == 'yes' ]]; then
    warn 'HTTP 只适合受信任的局域网。公网使用前请配置 HTTPS 反向代理和防火墙。'
  fi
  if [[ "$MODOU_BIND_ADDRESS" == '127.0.0.1' ]]; then
    warn '当前仅允许本机访问。手机需要访问时，请配置 HTTPS 反向代理，或确认防火墙后改为局域网监听。'
  fi
}

main() {
  detect_platform
  require_command curl '请先安装 curl。'
  require_command sed '请安装系统基础命令。'
  require_command grep '请安装系统基础命令。'
  detect_runtime
  if [[ "$DOCTOR_ONLY" -eq 1 ]]; then
    ok '环境检查通过，可以运行安装。'
    exit 0
  fi
  collect_inputs
  validate_inputs
  write_files
  export COUCHDB_USER="$MODOU_ADMIN_USER" COUCHDB_PASSWORD="$MODOU_ADMIN_PASSWORD"
  start_couchdb
  wait_for_couchdb
  verify_server_version
  ensure_database_and_user
  initialize_livesync
  print_result
}

main "$@"
