using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.Auth.AspNetCore;
using ZB.MOM.WW.MxGateway.Server;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
using ZB.MOM.WW.Secrets.Ui;

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

/// <summary>
/// Covers the authorization decision behind the side rail's Secrets link.
/// </summary>
/// <remarks>
/// <para>
/// The rail gates that item with <c>&lt;AuthorizeView Policy="secrets:manage"&gt;</c> — the same
/// policy the mounted <c>/admin/secrets</c> page enforces — rather than a role literal, so nav
/// visibility cannot drift from page access. These tests pin the policy's verdict per principal,
/// which is the behaviour the gate delegates to.
/// </para>
/// <para>
/// This is deliberately not a rendering test: the suite has no component-testing harness, and
/// adding one to assert a single <c>AuthorizeView</c> would be a large dependency for a small
/// claim. What is asserted here is the part that can actually be wrong — which principals the
/// policy admits. The link's presence in <c>MainLayout.razor</c> and the route's existence are
/// covered separately (see <c>GatewayApplicationTests</c>, which asserts <c>/admin/secrets</c> is
/// mapped), so an unmapped route cannot masquerade as a working link.
/// </para>
/// </remarks>
public sealed class SecretsNavGateTests
{
    /// <summary>An Administrator sees the Secrets link, because the policy admits that role.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task ManagePolicy_AdmitsAdministrator()
    {
        await using WebApplication app = GatewayApplication.Build([]);
        IAuthorizationService authorization = app.Services.GetRequiredService<IAuthorizationService>();

        AuthorizationResult result = await authorization.AuthorizeAsync(
            PrincipalWithRoles(DashboardRoles.Admin),
            resource: null,
            SecretsAuthorization.ManagePolicy);

        Assert.True(result.Succeeded);
    }

    /// <summary>
    /// A Viewer does not. This is the case the gate exists for: the secrets page denies a Viewer
    /// outright, so an ungated link would be a dead end rather than a degraded-but-useful view —
    /// which is why the sibling API Keys item is deliberately left ungated (that page does render
    /// read-only for Viewers).
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task ManagePolicy_RefusesViewer()
    {
        await using WebApplication app = GatewayApplication.Build([]);
        IAuthorizationService authorization = app.Services.GetRequiredService<IAuthorizationService>();

        AuthorizationResult result = await authorization.AuthorizeAsync(
            PrincipalWithRoles(DashboardRoles.Viewer),
            resource: null,
            SecretsAuthorization.ManagePolicy);

        Assert.False(result.Succeeded);
    }

    /// <summary>
    /// An unauthenticated principal does not. Covers the anonymous-localhost path, which grants a
    /// read-only identity without authenticating it.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task ManagePolicy_RefusesUnauthenticated()
    {
        await using WebApplication app = GatewayApplication.Build([]);
        IAuthorizationService authorization = app.Services.GetRequiredService<IAuthorizationService>();

        AuthorizationResult result = await authorization.AuthorizeAsync(
            new ClaimsPrincipal(new ClaimsIdentity()),
            resource: null,
            SecretsAuthorization.ManagePolicy);

        Assert.False(result.Succeeded);
    }

    // Mirrors what DashboardAuthenticator issues: roles as ZbClaimTypes.Role (== ClaimTypes.Role),
    // with the identity told to treat that claim as its role type. Constructing the identity with
    // an authentication type is what makes it authenticated — without one, every policy that
    // requires an authenticated user fails for the wrong reason and the role assertions above
    // would pass vacuously.
    private static ClaimsPrincipal PrincipalWithRoles(params string[] roles)
    {
        var identity = new ClaimsIdentity(
            roles.Select(role => new Claim(ZbClaimTypes.Role, role)),
            authenticationType: "Test",
            nameType: ZbClaimTypes.Name,
            roleType: ZbClaimTypes.Role);

        return new ClaimsPrincipal(identity);
    }
}
