namespace DodoSSH.Client.Auth;

/// <summary>What a token exchange returned.</summary>
/// <remarks>
/// The refresh token is the one long-lived credential the client holds, and the only part of this
/// that belongs in the OS keystore. The access token is short-lived and re-obtainable, and the ID
/// token is an assertion rather than a credential. None of them can open the vault: that needs the
/// passphrase, which is never stored anywhere.
/// </remarks>
/// <param name="AccessToken">Bearer token for the DodoSSH API.</param>
/// <param name="RefreshToken">Refresh token, when <c>offline_access</c> was granted.</param>
/// <param name="IdToken">Identity assertion, when <c>openid</c> was requested.</param>
/// <param name="ExpiresAtUtc">When the access token stops being accepted.</param>
/// <param name="Scope">Scopes actually granted, which may be narrower than those requested.</param>
public sealed record TokenSet(
    string AccessToken,
    string? RefreshToken,
    string? IdToken,
    DateTimeOffset ExpiresAtUtc,
    string? Scope)
{
    /// <summary>
    /// Whether the access token should be refreshed before use.
    /// </summary>
    /// <remarks>
    /// The margin exists because expiry is checked here and enforced by the server after a network
    /// round trip. Without it a token that is valid at the moment of the check is rejected by the
    /// time it arrives, which surfaces as a random 401 mid-sync.
    /// </remarks>
    /// <param name="clock">Time source.</param>
    /// <param name="margin">How far ahead to consider the token already expired.</param>
    public bool NeedsRefresh(TimeProvider clock, TimeSpan? margin = null)
    {
        ArgumentNullException.ThrowIfNull(clock);

        return clock.GetUtcNow() + (margin ?? TimeSpan.FromSeconds(60)) >= ExpiresAtUtc;
    }
}
