using ZB.MOM.WW.GalaxyRepository.Grpc;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;

namespace ZB.MOM.WW.MxGateway.Tests.Security.Authorization;

public sealed class GatewayGrpcScopeResolverTests
{
    /// <summary>Verifies that ResolveRequiredScope returns the expected scope for known RPC request types.</summary>
    /// <param name="requestType">The gRPC request type to test.</param>
    /// <param name="expectedScope">The expected scope for the request.</param>
    [Theory]
    [InlineData(typeof(OpenSessionRequest), GatewayScopes.SessionOpen)]
    [InlineData(typeof(CloseSessionRequest), GatewayScopes.SessionClose)]
    [InlineData(typeof(StreamEventsRequest), GatewayScopes.EventsRead)]
    [InlineData(typeof(AcknowledgeAlarmRequest), GatewayScopes.InvokeWrite)]
    [InlineData(typeof(StreamAlarmsRequest), GatewayScopes.EventsRead)]
    [InlineData(typeof(TestConnectionRequest), GatewayScopes.MetadataRead)]
    [InlineData(typeof(GetLastDeployTimeRequest), GatewayScopes.MetadataRead)]
    [InlineData(typeof(DiscoverHierarchyRequest), GatewayScopes.MetadataRead)]
    [InlineData(typeof(WatchDeployEventsRequest), GatewayScopes.MetadataRead)]
    [InlineData(typeof(BrowseChildrenRequest), GatewayScopes.MetadataRead)]
    public void ResolveRequiredScope_KnownRpcRequest_ReturnsExpectedScope(
        Type requestType,
        string expectedScope)
    {
        GatewayGrpcScopeResolver resolver = new();
        object request = Activator.CreateInstance(requestType)!;

        string scope = resolver.ResolveRequiredScope(request);

        Assert.Equal(expectedScope, scope);
    }

    /// <summary>Verifies that ResolveRequiredScope returns the expected scope for MXAccess invoke commands.</summary>
    /// <param name="commandKind">The MXAccess command kind to test.</param>
    /// <param name="expectedScope">The expected scope for the command.</param>
    [Theory]
    [InlineData(MxCommandKind.Register, GatewayScopes.InvokeRead)]
    [InlineData(MxCommandKind.AddItem, GatewayScopes.InvokeRead)]
    [InlineData(MxCommandKind.Advise, GatewayScopes.InvokeRead)]
    [InlineData(MxCommandKind.Write, GatewayScopes.InvokeWrite)]
    [InlineData(MxCommandKind.Write2, GatewayScopes.InvokeWrite)]
    [InlineData(MxCommandKind.WriteSecured, GatewayScopes.InvokeSecure)]
    [InlineData(MxCommandKind.WriteSecured2, GatewayScopes.InvokeSecure)]
    [InlineData(MxCommandKind.WriteBulk, GatewayScopes.InvokeWrite)]
    [InlineData(MxCommandKind.Write2Bulk, GatewayScopes.InvokeWrite)]
    [InlineData(MxCommandKind.WriteSecuredBulk, GatewayScopes.InvokeSecure)]
    [InlineData(MxCommandKind.WriteSecured2Bulk, GatewayScopes.InvokeSecure)]
    [InlineData(MxCommandKind.ReadBulk, GatewayScopes.InvokeRead)]
    [InlineData(MxCommandKind.AuthenticateUser, GatewayScopes.InvokeSecure)]
    [InlineData(MxCommandKind.ArchestraUserToId, GatewayScopes.MetadataRead)]
    [InlineData(MxCommandKind.GetSessionState, GatewayScopes.MetadataRead)]
    [InlineData(MxCommandKind.GetWorkerInfo, GatewayScopes.MetadataRead)]
    [InlineData(MxCommandKind.DrainEvents, GatewayScopes.EventsRead)]
    [InlineData(MxCommandKind.ShutdownWorker, GatewayScopes.Admin)]
    public void ResolveRequiredScope_InvokeCommand_ReturnsExpectedScope(
        MxCommandKind commandKind,
        string expectedScope)
    {
        GatewayGrpcScopeResolver resolver = new();

        string scope = resolver.ResolveRequiredScope(new MxCommandRequest
        {
            Command = new MxCommand
            {
                Kind = commandKind
            }
        });

        Assert.Equal(expectedScope, scope);
    }

    /// <summary>
    /// Verifies that an unmapped request type fails closed: the resolver returns the
    /// most-restrictive <see cref="GatewayScopes.Admin"/> scope rather than a permissive
    /// default, so a newly added RPC that is never mapped is denied to ordinary keys.
    /// </summary>
    [Fact]
    public void ResolveRequiredScope_UnmappedRequestType_FailsClosedToAdminScope()
    {
        GatewayGrpcScopeResolver resolver = new();

        string scope = resolver.ResolveRequiredScope(new UnmappedRequest());

        Assert.Equal(GatewayScopes.Admin, scope);
    }

    /// <summary>
    /// Verifies that an <see cref="MxCommandRequest"/> with an unrecognized command kind
    /// resolves to the read scope rather than silently granting write or admin access.
    /// </summary>
    [Fact]
    public void ResolveRequiredScope_UnknownInvokeCommandKind_ReturnsInvokeReadScope()
    {
        GatewayGrpcScopeResolver resolver = new();

        string scope = resolver.ResolveRequiredScope(new MxCommandRequest
        {
            Command = new MxCommand
            {
                Kind = (MxCommandKind)9999,
            },
        });

        Assert.Equal(GatewayScopes.InvokeRead, scope);
    }

    /// <summary>Request type intentionally not mapped by the scope resolver.</summary>
    private sealed class UnmappedRequest;
}
