using System.Security.Claims;
using Microsoft.AspNetCore.DataProtection;
using ZB.MOM.WW.MxGateway.Server.Dashboard;

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

public sealed class HubTokenServiceTests
{
    /// <summary>
    /// Server-039: a token whose data-protected payload has both
    /// <c>Name</c> and <c>NameIdentifier</c> null (the principal that
    /// minted the token had no identity claims) must be rejected by
    /// <see cref="HubTokenService.Validate"/>. The role claims alone are
    /// not enough — without a caller identity, the resulting
    /// <see cref="ClaimsPrincipal"/> would satisfy
    /// <c>IsAuthenticated</c> / <c>IsInRole</c> checks without an
    /// associated user.
    /// </summary>
    [Fact]
    public void Validate_TokenWithNullNameAndNullNameIdentifier_ReturnsNull()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        // Issue from a principal with NO Name claim and NO NameIdentifier
        // claim. The Issue method's payload will then carry
        // (Name = null, NameIdentifier = null, Roles = ["Viewer"]).
        ClaimsIdentity identity = new(
            [new Claim(ClaimTypes.Role, DashboardRoles.Viewer)],
            authenticationType: "test");
        ClaimsPrincipal principal = new(identity);
        string token = service.Issue(principal);

        ClaimsPrincipal? result = service.Validate(token);

        Assert.Null(result);
    }

    /// <summary>
    /// Sanity check: a token minted from a principal with a Name claim
    /// validates and returns a principal carrying that identity. Pins
    /// that the Server-039 fix does not over-reject valid tokens.
    /// </summary>
    [Fact]
    public void Validate_TokenWithName_ReturnsAuthenticatedPrincipal()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        ClaimsIdentity identity = new(
            [
                new Claim(ClaimTypes.Name, "alice"),
                new Claim(ClaimTypes.NameIdentifier, "alice-id"),
                new Claim(ClaimTypes.Role, DashboardRoles.Admin),
            ],
            authenticationType: "test",
            nameType: ClaimTypes.Name,
            roleType: ClaimTypes.Role);
        ClaimsPrincipal principal = new(identity);
        string token = service.Issue(principal);

        ClaimsPrincipal? result = service.Validate(token);

        Assert.NotNull(result);
        Assert.Equal("alice", result.Identity?.Name);
        Assert.True(result.IsInRole(DashboardRoles.Admin));
    }

    /// <summary>
    /// Sanity check: a token minted with only a NameIdentifier (no Name)
    /// still validates — a non-null caller identity is the contract,
    /// either field is sufficient.
    /// </summary>
    [Fact]
    public void Validate_TokenWithOnlyNameIdentifier_ReturnsPrincipal()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        ClaimsIdentity identity = new(
            [
                new Claim(ClaimTypes.NameIdentifier, "alice-id"),
                new Claim(ClaimTypes.Role, DashboardRoles.Viewer),
            ],
            authenticationType: "test");
        ClaimsPrincipal principal = new(identity);
        string token = service.Issue(principal);

        ClaimsPrincipal? result = service.Validate(token);

        Assert.NotNull(result);
        Assert.True(result.IsInRole(DashboardRoles.Viewer));
    }

    /// <summary>Verifies that a null token returns null.</summary>
    [Fact]
    public void Validate_NullToken_ReturnsNull()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        Assert.Null(service.Validate(null));
    }

    /// <summary>Verifies that an empty token returns null.</summary>
    [Fact]
    public void Validate_EmptyToken_ReturnsNull()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        Assert.Null(service.Validate(string.Empty));
    }

    /// <summary>Verifies that an invalid token returns null.</summary>
    [Fact]
    public void Validate_GarbageToken_ReturnsNull()
    {
        HubTokenService service = new(new EphemeralDataProtectionProvider());

        Assert.Null(service.Validate("this-is-not-a-protected-payload"));
    }
}
