using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Authority.RateLimiting;
using Xunit;

namespace StellaOps.Authority.Tests.RateLimiting;

public class AuthorityRateLimiterMetadataMiddlewareTests
{
    [Fact]
    public async Task InvokeAsync_ExtractsClientIdFromBasicAuthorization()
    {
        var context = new DefaultHttpContext();
        context.Request.Path = "/token";
        context.Request.Method = HttpMethods.Post;
        context.Connection.RemoteIpAddress = IPAddress.Parse("198.51.100.10");
        context.Request.Headers.Authorization = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("clientA:secret"));

        var middleware = CreateMiddleware();
        await middleware.InvokeAsync(context);

        var metadata = context.Features.Get<IAuthorityRateLimiterFeature>()?.Metadata;
        Assert.NotNull(metadata);
        Assert.Equal("/token", metadata!.Endpoint);
        Assert.Equal("clientA", metadata.ClientId);
        Assert.Equal("198.51.100.10", metadata.RemoteIp);
    }

    [Fact]
    public async Task InvokeAsync_ExtractsClientIdFromFormBody()
    {
        var body = "grant_type=password&client_id=form-client";
        var bytes = Encoding.UTF8.GetBytes(body);

        var context = new DefaultHttpContext();
        context.Request.Path = "/token";
        context.Request.Method = HttpMethods.Post;
        context.Connection.RemoteIpAddress = IPAddress.Parse("198.51.100.20");
        context.Request.ContentType = "application/x-www-form-urlencoded";
        context.Request.Body = new MemoryStream(bytes);
        context.Request.ContentLength = bytes.Length;

        var middleware = CreateMiddleware();
        await middleware.InvokeAsync(context);

        var metadata = context.Features.Get<IAuthorityRateLimiterFeature>()?.Metadata;
        Assert.NotNull(metadata);
        Assert.Equal("form-client", metadata!.ClientId);
        Assert.Equal(0, context.Request.Body.Position);
    }

    [Fact]
    public async Task InvokeAsync_ExtractsClientIdFromAuthorizeQuery()
    {
        var context = new DefaultHttpContext();
        context.Request.Path = "/authorize";
        context.Request.QueryString = new QueryString("?client_id=browser-app");

        var middleware = CreateMiddleware();
        await middleware.InvokeAsync(context);

        var metadata = context.Features.Get<IAuthorityRateLimiterFeature>()?.Metadata;
        Assert.NotNull(metadata);
        Assert.Equal("browser-app", metadata!.ClientId);
    }

    [Fact]
    public async Task InvokeAsync_PrefersForwardedFor_WhenRemoteIpMissing()
    {
        var context = new DefaultHttpContext();
        context.Request.Path = "/token";
        context.Request.Method = HttpMethods.Post;
        context.Request.Headers["X-Forwarded-For"] = "203.0.113.99";
        context.Request.Headers.UserAgent = "StellaOps-Client/1.2";

        var middleware = CreateMiddleware();
        await middleware.InvokeAsync(context);

        var metadata = context.Features.Get<IAuthorityRateLimiterFeature>()?.Metadata;
        Assert.NotNull(metadata);
        Assert.Equal("203.0.113.99", metadata!.RemoteIp);
        Assert.Equal("203.0.113.99", metadata.ForwardedFor);
        Assert.Equal("StellaOps-Client/1.2", metadata.UserAgent);
        Assert.True(metadata.Tags.TryGetValue("authority.user_agent", out var tagValue));
        Assert.Equal("StellaOps-Client/1.2", tagValue);
    }

    private static AuthorityRateLimiterMetadataMiddleware CreateMiddleware()
    {
        return new AuthorityRateLimiterMetadataMiddleware(
            _ => Task.CompletedTask,
            TimeProvider.System,
            NullLogger<AuthorityRateLimiterMetadataMiddleware>.Instance);
    }
}
