using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Common.Settings;

namespace StartupHelpers;

public static class RateLimitingExtensions
{
    public static void AddPublicApiRateLimiting(
        this IServiceCollection services,
        IConfiguration configuration,
        string sectionName = "RateLimiting")
    {
        var settings = configuration.GetSection(sectionName).Get<RateLimitingSettings>()
                       ?? new RateLimitingSettings();

        services.AddRateLimiter(options =>
        {
            var global = settings.Global ?? new RateLimitPolicySettings();
            options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
            {
                var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
                return RateLimitPartition.GetFixedWindowLimiter(
                    partitionKey: ip,
                    factory: _ => new FixedWindowRateLimiterOptions
                    {
                        PermitLimit = global.PermitLimit,
                        Window = global.Window,
                        QueueLimit = global.QueueLimit,
                        AutoReplenishment = global.AutoReplenishment
                    });
            });

            foreach (var entry in settings.Policies)
            {
                var policyName = entry.Key;
                var policy = entry.Value ?? new RateLimitPolicySettings();

                options.AddPolicy(policyName, httpContext =>
                {
                    var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
                    return RateLimitPartition.GetFixedWindowLimiter(
                        partitionKey: ip,
                        factory: _ => new FixedWindowRateLimiterOptions
                        {
                            PermitLimit = policy.PermitLimit,
                            Window = policy.Window,
                            QueueLimit = policy.QueueLimit,
                            AutoReplenishment = policy.AutoReplenishment
                        });
                });
            }

            options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
            options.OnRejected = async (context, ct) =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILoggerFactory>()
                    .CreateLogger("RateLimiting");

                var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
                var endpoint = context.HttpContext.Request.Path;

                logger.LogWarning("Rate limit exceeded for {Endpoint} from IP {IP}", endpoint, ip);

                context.HttpContext.Response.ContentType = "application/json";
                await context.HttpContext.Response.WriteAsync("""{"error":"Too many requests. Try again later."}""", ct);
            };
        });
    }
}
