using System.Net;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Http;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Dashboard;

namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;

/// <summary>
/// Tests the <c>POST /auth/login</c> fixed-window per-IP rate limiter. Exercised through the same
/// partition factory the production policy registers, so the test pins the real permit limit and
/// per-IP partitioning without standing up an HTTP server.
/// </summary>
public sealed class DashboardLoginRateLimitTests
{
    /// <summary>A burst beyond the permit limit from one IP has its excess attempts rejected (HTTP 429 in the pipeline).</summary>
    [Fact]
    public void LoginRateLimiter_BurstBeyondPermitLimit_RejectsExcessFromSameIp()
    {
        SecurityOptions security = new()
        {
            LoginRateLimitPermitLimit = 3,
            LoginRateLimitWindowSeconds = 60,
        };
        using PartitionedRateLimiter<HttpContext> limiter =
            DashboardEndpointRouteBuilderExtensions.CreateLoginRateLimiter(security);
        HttpContext context = ContextWithIp("10.0.0.7");

        for (int i = 0; i < 3; i++)
        {
            using RateLimitLease lease = limiter.AttemptAcquire(context);
            Assert.True(lease.IsAcquired);
        }

        using RateLimitLease rejected = limiter.AttemptAcquire(context);
        Assert.False(rejected.IsAcquired);
    }

    /// <summary>Distinct client IPs are partitioned independently — one IP's burst does not starve another.</summary>
    [Fact]
    public void LoginRateLimiter_DistinctIps_PartitionedIndependently()
    {
        SecurityOptions security = new()
        {
            LoginRateLimitPermitLimit = 1,
            LoginRateLimitWindowSeconds = 60,
        };
        using PartitionedRateLimiter<HttpContext> limiter =
            DashboardEndpointRouteBuilderExtensions.CreateLoginRateLimiter(security);

        using RateLimitLease first = limiter.AttemptAcquire(ContextWithIp("10.0.0.1"));
        using RateLimitLease exhaustedForFirst = limiter.AttemptAcquire(ContextWithIp("10.0.0.1"));
        using RateLimitLease second = limiter.AttemptAcquire(ContextWithIp("10.0.0.2"));

        Assert.True(first.IsAcquired);
        Assert.False(exhaustedForFirst.IsAcquired);
        Assert.True(second.IsAcquired);
    }

    private static HttpContext ContextWithIp(string ip)
    {
        DefaultHttpContext context = new();
        context.Connection.RemoteIpAddress = IPAddress.Parse(ip);
        return context;
    }
}
