using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Configuration;

namespace ZB.MOM.WW.MxGateway.Server.Dashboard;

/// <summary>
/// Authorizes a dashboard request by checking either: (a) the LDAP-issued
/// role claim satisfies <see cref="DashboardAuthorizationRequirement.RequiredRoles"/>,
/// (b) authentication is fully disabled, or (c) the request is from loopback
/// and <c>MxGateway:Dashboard:AllowAnonymousLocalhost</c> is on.
/// </summary>
/// <remarks>
/// The environment bypasses in (b) and (c) grant <em>read-only</em> access only: they
/// satisfy a requirement that includes <see cref="DashboardRoles.Viewer"/> (i.e.
/// <see cref="DashboardAuthorizationRequirement.AnyDashboardRole"/>) but never the
/// <see cref="DashboardAuthorizationRequirement.AdminOnly"/> requirement. Destructive/admin
/// surfaces still require a real Admin role claim, preserving the documented
/// "anonymous localhost is read-only" contract at the policy layer rather than by accident
/// in downstream service re-checks. The loopback test trusts
/// <c>Connection.RemoteIpAddress</c>; if forwarded-headers middleware is ever added upstream,
/// <see cref="IsLoopbackRequest"/> must be revisited so a spoofed <c>X-Forwarded-For</c>
/// cannot masquerade as loopback.
/// </remarks>
public sealed class DashboardAuthorizationHandler(
    IHttpContextAccessor httpContextAccessor,
    IOptions<GatewayOptions> options) : AuthorizationHandler<DashboardAuthorizationRequirement>
{
    /// <inheritdoc />
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        DashboardAuthorizationRequirement requirement)
    {
        GatewayOptions gatewayOptions = options.Value;

        // Environment bypasses are read-only: they satisfy the Viewer requirement
        // (AnyDashboardRole) but never AdminOnly, which lacks Viewer in RequiredRoles.
        bool grantsReadOnly = requirement.RequiredRoles.Contains(DashboardRoles.Viewer);

        if (grantsReadOnly && gatewayOptions.Authentication.Mode == AuthenticationMode.Disabled)
        {
            context.Succeed(requirement);

            return Task.CompletedTask;
        }

        if (grantsReadOnly && gatewayOptions.Dashboard.AllowAnonymousLocalhost && IsLoopbackRequest())
        {
            context.Succeed(requirement);

            return Task.CompletedTask;
        }

        if (context.User.Identity?.IsAuthenticated != true)
        {
            return Task.CompletedTask;
        }

        foreach (string role in requirement.RequiredRoles)
        {
            if (context.User.IsInRole(role))
            {
                context.Succeed(requirement);
                return Task.CompletedTask;
            }
        }

        return Task.CompletedTask;
    }

    private bool IsLoopbackRequest()
    {
        IPAddress? remoteAddress = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;

        return remoteAddress is not null && IPAddress.IsLoopback(remoteAddress);
    }
}
