My understanding is &x returns the address of the pointer x, *&x returns the address that x points too, IT RETURNS A ADDRESS NOT ANOTHER POINTER. So how is the type a pointer to pointer? As far as i can see:
Let's walk through this step by step and make sure we are being precise about all the terminology.
x is of type struct node *, i.e., a pointer-to-node. It is, therefore, a pointer.
&x is an expression (in the same way that 1 + 2 is an expression). Expressions do not "return" anything; they evaluate to some result which has a value and a type. In this case, we apply the & operator to x. The resulting value has a type of pointer-to-pointer-to-node (in C syntax, struct node**). You don't need to do any difficult thinking to understand this: the type that results from & is pointer-to-(whatever was the type of the thing that & is applied to). In our case, the (whatever was the type of the thing that & is applied to) is "pointer-to-node", and we directly substitute that in: typeof(&x) is (typeof x)* which is (struct node *)* which is struct node **. (I don't think you can actually use (typeof x)* as a type declaration; I'm just showing the type calculus here in something that looks vaguely C-ish.)
The value of that expression is the address of the existing x variable. That's what & does. The issue here is that "a address not another pointer" makes no sense, because "address" isn't a different kind of type. When we say "the address of x", we mean "a thing which has a type that is pointer-to-(whatever is the type of x), and a value which tells us where x is located in memory".
When we write *&x, that is another expression, in which we apply the * operator to the subexpression &x. Functionally, * undoes the effect of &; it means "the thing at the specified location", and the type has one level of "pointer-to" removed.
de-referencing x is the same as *x which is of type (struct node *)
Careful with your language. *x indeed "is the same as dereferencing x", which is to say, it is the way that we express that idea in code. x is a pointer of type struct node *; thus, *x is the pointed-at value, of type struct node.
printf("\n&x address is: %p\n", &x);
printf("*&x address is: %p\n", *&x);
I think this is where you confused yourself. The thing that you labelled as "&x address" is not the address of &x, but instead &x itself, i.e. the address of x. (Just like how in the first printf you printed the very same thing - &x - and labelled it "address of x".) Of course printing *&x gives you the same thing as printing &y, because *&x is just x (the * undoes the &), which is &y (because that's how x was initialized). You can do this kind of reasoning without even needing to think about the types (although you will need to think about types in the long run, in order to write correct code).
There are, of course, limitations: you cannot just write pairs of & and * together as much as you want and have them cancel out. * needs to be applied to something that is a pointer, and using & later can't save you from that requirement. Similarly, & needs to be applied to something that has a defined location, and using * later can't save you from that requirement. In particular, you can't apply consecutive && to something, because the address produced by the first & is temporary and doesn't conceptually have an address (in the compiled code, it might for example only ever appear in a register and never be written to memory).