Why are all these different codes and tokens needed? Why not just provide an authorization code with a lifetime of 90 days and be done with it?
The authorization code is one of several ways of delivering the access token – it's not a separate "step" but still part of the initial "interactive authentication" flow. It differs from the actual access token in that you also need the OAuth2 client secret (or the PKCE challenge that was generated at the beginning) to make use of the code. In theory, this means that even if the code itself is intercepted (e.g. through redirects being visible client-side), only the original app can exchange it for the real token.
There are other flows which do not use one – you could use the "implicit grant" flow to directly receive the access token, for example, or you could use a certificate to authenticate and receive the token in one step, depending on what the OAuth IDP supports.
(For example, "service accounts" in Google authenticate using an RSA keypair to get an access token – they don't use the interactive auth flow and therefore there is no auth code.)
Am I correct in assuming that if my Refresh Token expires, then I have to go right back to the beginning of the chain and request access from my user again?
Yes, but avoiding that is actually the purpose of a Refresh Token in the first place.
If you only had one type of token (an Access Token), every time it expired you would need to go right back to the beginning of the chain. This is usually avoided by having a Refresh Token which normally has a much longer lifetime, e.g. if the Access Token is valid for several hours or days, then it's common for a the Refresh Token to be valid for months or years.
I would say that 90 days for a Refresh Token is unusually short, and somewhat defeats the point of having one – my guess is that Azure OAuth2 is specifically set up that way in order to force reauthentication through a harder-to-steal credential, such as a device certificate or the custom HMAC scheme "Windows Hello" uses. (In contrast, refresh tokens issued by Google seem to have practically no expiry time at all – I've been using the same token to access Gmail IMAP for years.)