using System.Net;
using System.Net.Sockets;

namespace UniVerse.Api.Middleware;

public sealed class LocalNetworksOnlyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<LocalNetworksOnlyMiddleware> _logger;

    public LocalNetworksOnlyMiddleware(RequestDelegate next, ILogger<LocalNetworksOnlyMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var remoteIpAddress = context.Connection.RemoteIpAddress;

        if (remoteIpAddress is null || !IsLocalNetwork(remoteIpAddress))
        {
            _logger.LogWarning("Blocked metrics request from non-local address {RemoteIpAddress}", remoteIpAddress);
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsync("Metrics endpoint is available only from local networks.");
            return;
        }

        await _next(context);
    }

    private static bool IsLocalNetwork(IPAddress ipAddress)
    {
        if (IPAddress.IsLoopback(ipAddress))
        {
            return true;
        }

        if (ipAddress.IsIPv4MappedToIPv6)
        {
            ipAddress = ipAddress.MapToIPv4();
        }

        return ipAddress.AddressFamily switch
        {
            AddressFamily.InterNetwork => IsPrivateOrLinkLocalIPv4(ipAddress),
            AddressFamily.InterNetworkV6 => IsPrivateOrLinkLocalIPv6(ipAddress),
            _ => false
        };
    }

    private static bool IsPrivateOrLinkLocalIPv4(IPAddress ipAddress)
    {
        var bytes = ipAddress.GetAddressBytes();

        return bytes[0] == 10
               || bytes[0] == 127
               || (bytes[0] == 192 && bytes[1] == 168)
               || (bytes[0] == 172 && bytes[1] is >= 16 and <= 31)
               || (bytes[0] == 169 && bytes[1] == 254);
    }

    private static bool IsPrivateOrLinkLocalIPv6(IPAddress ipAddress)
    {
        var bytes = ipAddress.GetAddressBytes();

        return ipAddress.IsIPv6LinkLocal
               || ipAddress.IsIPv6SiteLocal
               || (bytes[0] & 0xfe) == 0xfc;
    }
}
