using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace StellaOps.Auth.Client;

/// <summary>
/// In-memory token cache suitable for service scenarios.
/// </summary>
public sealed class InMemoryTokenCache : IStellaOpsTokenCache
{
    private readonly ConcurrentDictionary<string, StellaOpsTokenCacheEntry> entries = new(StringComparer.Ordinal);
    private readonly TimeProvider timeProvider;
    private readonly Func<StellaOpsTokenCacheEntry, StellaOpsTokenCacheEntry> normalizer;
    private readonly TimeSpan expirationSkew;

    public InMemoryTokenCache(TimeProvider? timeProvider = null, TimeSpan? expirationSkew = null)
    {
        this.timeProvider = timeProvider ?? TimeProvider.System;
        this.expirationSkew = expirationSkew ?? TimeSpan.FromSeconds(30);
        normalizer = static entry => entry.NormalizeScopes();
    }

    public ValueTask<StellaOpsTokenCacheEntry?> GetAsync(string key, CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(key);

        if (!entries.TryGetValue(key, out var entry))
        {
            return ValueTask.FromResult<StellaOpsTokenCacheEntry?>(null);
        }

        if (entry.IsExpired(timeProvider, expirationSkew))
        {
            entries.TryRemove(key, out _);
            return ValueTask.FromResult<StellaOpsTokenCacheEntry?>(null);
        }

        return ValueTask.FromResult<StellaOpsTokenCacheEntry?>(entry);
    }

    public ValueTask SetAsync(string key, StellaOpsTokenCacheEntry entry, CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(key);
        ArgumentNullException.ThrowIfNull(entry);

        entries[key] = normalizer(entry);
        return ValueTask.CompletedTask;
    }

    public ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(key);
        entries.TryRemove(key, out _);
        return ValueTask.CompletedTask;
    }
}
