using System;
using Microsoft.AspNetCore.Http;

namespace StellaOps.Authority.RateLimiting;

/// <summary>
/// Provides access to the rate limiter metadata for the current HTTP request.
/// </summary>
internal interface IAuthorityRateLimiterMetadataAccessor
{
    /// <summary>
    /// Retrieves the metadata for the current request, if available.
    /// </summary>
    /// <returns>The metadata instance or null when no HTTP context is present.</returns>
    AuthorityRateLimiterMetadata? GetMetadata();

    /// <summary>
    /// Updates the client identifier associated with the current request.
    /// </summary>
    void SetClientId(string? clientId);

    /// <summary>
    /// Updates the subject identifier associated with the current request.
    /// </summary>
    void SetSubjectId(string? subjectId);

    /// <summary>
    /// Adds or removes a metadata tag for the current request.
    /// </summary>
    void SetTag(string name, string? value);
}

internal sealed class AuthorityRateLimiterMetadataAccessor : IAuthorityRateLimiterMetadataAccessor
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public AuthorityRateLimiterMetadataAccessor(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public AuthorityRateLimiterMetadata? GetMetadata()
    {
        return TryGetMetadata();
    }

    public void SetClientId(string? clientId)
    {
        var metadata = TryGetMetadata();
        if (metadata is not null)
        {
            metadata.ClientId = Normalize(clientId);
            metadata.SetTag("authority.client_id", metadata.ClientId);
        }
    }

    public void SetSubjectId(string? subjectId)
    {
        var metadata = TryGetMetadata();
        if (metadata is not null)
        {
            metadata.SubjectId = Normalize(subjectId);
            metadata.SetTag("authority.subject_id", metadata.SubjectId);
        }
    }

    public void SetTag(string name, string? value)
    {
        var metadata = TryGetMetadata();
        metadata?.SetTag(name, value);
    }

    private AuthorityRateLimiterMetadata? TryGetMetadata()
    {
        var context = httpContextAccessor.HttpContext;
        return context?.Features.Get<IAuthorityRateLimiterFeature>()?.Metadata;
    }

    private static string? Normalize(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value;
    }
}
