function rule::apply() {
  local rule_name="$1"
  local client_ip="$2"

  rule::require_exists "$rule_name" || return 1

  # Check if already applied
  local peer_name
  peer_name=$(peers::find_by_ip "$client_ip")
  if [[ -n "$peer_name" ]]; then
    # Check if already applied via iptables
    if rule::is_applied "$rule_name" "$client_ip"; then
      log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
      return 0
    fi
  fi

  local rule_file
  rule_file="$(ctx::rule::path "${rule_name}.rule")"

  # Process block_ips
  while IFS= read -r ip; do
    [[ -z "$ip" ]] && continue
    firewall::block_ip "$client_ip" "$ip"
  done < <(rule::get "$rule_name" "block_ips")

  # Process block_ports
  while IFS= read -r entry; do
    [[ -z "$entry" ]] && continue
    local target port proto
    IFS=":" read -r target port proto <<< "$entry"
    proto="${proto:-tcp}"
    firewall::block_port "$client_ip" "$target" "$port" "$proto"
  done < <(rule::get "$rule_name" "block_ports")

  # Process allow_ips (inserted before blocks)
  while IFS= read -r ip; do
    [[ -z "$ip" ]] && continue
    firewall::allow_ip "$client_ip" "$ip"
  done < <(rule::get "$rule_name" "allow_ips")

  # allow_ports (inserted last = highest priority)
  while IFS= read -r entry; do
    [[ -z "$entry" ]] && continue
    local target port proto
    IFS=":" read -r target port proto <<< "$entry"
    proto="${proto:-tcp}"
    firewall::allow_port "$client_ip" "$target" "$port" "$proto"
  done < <(rule::get "$rule_name" "allow_ports")

  # Persist rule assignment in meta
  log::debug "rule::apply: peer_name=$peer_name ip=$client_ip"
  if [[ -n "$peer_name" ]]; then
    peers::set_meta "$peer_name" "rule" "$rule_name"
    log::debug "rule::apply: set meta rule=$rule_name for $peer_name"
  fi

  local dns_redirect
  dns_redirect=$(rule::get "$rule_name" "dns_redirect")

  if [[ "$dns_redirect" == "true" ]]; then
      local subnet
      subnet=$(config::subnet_for "$(peers::get_meta "$peer_name" "subtype")")
      rule::apply_dns_redirect "${subnet}.0/24"
  fi

  log::wg_preset "Applied rule '${rule_name}' to: ${client_ip}"
}