using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Baya.WebFramework.ServiceConfiguration;

public static class ForwardedHeadersServiceExtension
{
    /// <summary>Configuration section holding the trusted reverse-proxy addresses/networks.</summary>
    public const string ConfigSection = "ForwardedHeaders";

    /// <summary>
    /// Configures the forwarded-headers middleware so that, behind a reverse proxy, the resolved client
    /// address (from <c>X-Forwarded-For</c>) — not the proxy's address — is what
    /// <c>HttpContext.Connection.RemoteIpAddress</c> reports. The rate limiter partitions on that address,
    /// so without this every client behind the proxy shares a single bucket (self-DoS). Only proxies listed
    /// in <c>ForwardedHeaders:KnownProxies</c> / <c>:KnownNetworks</c> (plus loopback) are trusted; an
    /// untrusted hop's forwarded header is ignored, so a client can't spoof its address. Pair with
    /// <c>app.UseForwardedHeaders()</c> placed first in the pipeline (before anything reads the client IP).
    /// </summary>
    public static IServiceCollection AddForwardedHeadersConfiguration(this IServiceCollection services, IConfiguration configuration)
    {
        var knownProxies = configuration.GetSection($"{ConfigSection}:KnownProxies").Get<string[]>() ?? [];
        var knownNetworks = configuration.GetSection($"{ConfigSection}:KnownNetworks").Get<string[]>() ?? [];

        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

            // The framework defaults trust only loopback; extend that to the deployment's real proxy hop(s).
            foreach (var proxy in knownProxies)
                if (IPAddress.TryParse(proxy, out var address))
                    options.KnownProxies.Add(address);

            foreach (var network in knownNetworks)
            {
                var parts = network.Split('/', 2);
                if (parts.Length == 2 && IPAddress.TryParse(parts[0], out var prefix) && int.TryParse(parts[1], out var prefixLength))
                    options.KnownIPNetworks.Add(new System.Net.IPNetwork(prefix, prefixLength));
            }
        });

        return services;
    }
}
