using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using StellaOps.Authority.RateLimiting;
using StellaOps.Configuration;
using Xunit;

namespace StellaOps.Authority.Tests.RateLimiting;

public class AuthorityRateLimiterIntegrationTests
{
    [Fact]
    public async Task TokenEndpoint_Returns429_WhenLimitExceeded()
    {
        using var server = CreateServer(options =>
        {
            options.Security.RateLimiting.Token.PermitLimit = 1;
            options.Security.RateLimiting.Token.QueueLimit = 0;
            options.Security.RateLimiting.Token.Window = TimeSpan.FromSeconds(30);
        });

        using var client = server.CreateClient();
        client.DefaultRequestHeaders.Add("X-Forwarded-For", "198.51.100.50");

        var firstResponse = await client.PostAsync("/token", CreateTokenForm("feedser"));
        Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);

        var secondResponse = await client.PostAsync("/token", CreateTokenForm("feedser"));
        Assert.Equal(HttpStatusCode.TooManyRequests, secondResponse.StatusCode);
        Assert.NotNull(secondResponse.Headers.RetryAfter);
    }


    [Fact]
    public async Task TokenEndpoint_AllowsDifferentClientIdsWithinWindow()
    {
        using var server = CreateServer(options =>
        {
            options.Security.RateLimiting.Token.PermitLimit = 1;
            options.Security.RateLimiting.Token.QueueLimit = 0;
            options.Security.RateLimiting.Token.Window = TimeSpan.FromSeconds(30);
        });

        using var client = server.CreateClient();
        client.DefaultRequestHeaders.Add("X-Forwarded-For", "198.51.100.70");

        var firstResponse = await client.PostAsync("/token", CreateTokenForm("alpha-client"));
        Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);

        var secondResponse = await client.PostAsync("/token", CreateTokenForm("beta-client"));
        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
    }

    [Fact]
    public async Task InternalEndpoint_Returns429_WhenLimitExceeded()
    {
        using var server = CreateServer(options =>
        {
            options.Security.RateLimiting.Internal.Enabled = true;
            options.Security.RateLimiting.Internal.PermitLimit = 1;
            options.Security.RateLimiting.Internal.QueueLimit = 0;
            options.Security.RateLimiting.Internal.Window = TimeSpan.FromSeconds(15);
        });

        using var client = server.CreateClient();
        client.DefaultRequestHeaders.Add("X-Forwarded-For", "198.51.100.60");

        var firstResponse = await client.GetAsync("/internal/ping");
        Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);

        var secondResponse = await client.GetAsync("/internal/ping");
        Assert.Equal(HttpStatusCode.TooManyRequests, secondResponse.StatusCode);
    }

    private static TestServer CreateServer(Action<StellaOpsAuthorityOptions>? configure)
    {
        var options = new StellaOpsAuthorityOptions
        {
            Issuer = new Uri("https://authority.integration.test"),
            SchemaVersion = 1
        };
        options.Storage.ConnectionString = "mongodb://localhost/authority";

        configure?.Invoke(options);

        var builder = new WebHostBuilder()
            .ConfigureServices(services =>
            {
                services.AddSingleton(options);
                services.AddSingleton<IOptions<StellaOpsAuthorityOptions>>(Options.Create(options));
                services.TryAddSingleton<TimeProvider>(_ => TimeProvider.System);
                services.AddHttpContextAccessor();
                services.TryAddSingleton<IAuthorityRateLimiterMetadataAccessor, AuthorityRateLimiterMetadataAccessor>();
                services.TryAddSingleton<IAuthorityRateLimiterPartitionKeyResolver, DefaultAuthorityRateLimiterPartitionKeyResolver>();
                services.AddRateLimiter(rateLimiterOptions =>
                {
                    AuthorityRateLimiter.Configure(rateLimiterOptions, options);
                });
            })
            .Configure(app =>
            {
                app.UseAuthorityRateLimiterContext();
                app.UseRateLimiter();

                app.Map("/token", builder =>
                {
                    builder.Run(async context =>
                    {
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        await context.Response.WriteAsync("token-ok");
                    });
                });

                app.Map("/internal/ping", builder =>
                {
                    builder.Run(async context =>
                    {
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        await context.Response.WriteAsync("internal-ok");
                    });
                });
            });

        return new TestServer(builder);
    }

    private static FormUrlEncodedContent CreateTokenForm(string clientId)
        => new(new Dictionary<string, string>
        {
            ["grant_type"] = "client_credentials",
            ["client_id"] = clientId
        });
}
