using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace StellaOps.Auth.Client;

/// <summary>
/// Caches JWKS documents for Authority.
/// </summary>
public sealed class StellaOpsJwksCache
{
    private readonly HttpClient httpClient;
    private readonly StellaOpsDiscoveryCache discoveryCache;
    private readonly IOptionsMonitor<StellaOpsAuthClientOptions> optionsMonitor;
    private readonly TimeProvider timeProvider;
    private readonly ILogger<StellaOpsJwksCache>? logger;

    private JsonWebKeySet? cachedSet;
    private DateTimeOffset cacheExpiresAt;

    public StellaOpsJwksCache(
        HttpClient httpClient,
        StellaOpsDiscoveryCache discoveryCache,
        IOptionsMonitor<StellaOpsAuthClientOptions> optionsMonitor,
        TimeProvider? timeProvider = null,
        ILogger<StellaOpsJwksCache>? logger = null)
    {
        this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        this.discoveryCache = discoveryCache ?? throw new ArgumentNullException(nameof(discoveryCache));
        this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
        this.timeProvider = timeProvider ?? TimeProvider.System;
        this.logger = logger;
    }

    public async Task<JsonWebKeySet> GetAsync(CancellationToken cancellationToken)
    {
        var now = timeProvider.GetUtcNow();
        if (cachedSet is not null && now < cacheExpiresAt)
        {
            return cachedSet;
        }

        var configuration = await discoveryCache.GetAsync(cancellationToken).ConfigureAwait(false);

        logger?.LogDebug("Fetching JWKS from {JwksUri}.", configuration.JwksEndpoint);

        using var response = await httpClient.GetAsync(configuration.JwksEndpoint, cancellationToken).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
        cachedSet = new JsonWebKeySet(json);
        cacheExpiresAt = now + optionsMonitor.CurrentValue.JwksCacheLifetime;

        return cachedSet;
    }
}
