using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;

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

public sealed class GatewayGrpcAuthorizationInterceptor(
    IApiKeyVerifier apiKeyVerifier,
    GatewayGrpcScopeResolver scopeResolver,
    IGatewayRequestIdentityAccessor identityAccessor,
    IOptions<GatewayOptions> options) : Interceptor
{
    /// <inheritdoc />
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        ApiKeyIdentity? identity = await AuthenticateAndAuthorizeAsync(request, context).ConfigureAwait(false);
        IDisposable? identityScope = identity is null ? null : identityAccessor.Push(identity);
        using (identityScope)
        {
            return await continuation(request, context).ConfigureAwait(false);
        }
    }

    /// <inheritdoc />
    public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
        TRequest request,
        IServerStreamWriter<TResponse> responseStream,
        ServerCallContext context,
        ServerStreamingServerMethod<TRequest, TResponse> continuation)
    {
        ApiKeyIdentity? identity = await AuthenticateAndAuthorizeAsync(request, context).ConfigureAwait(false);
        IDisposable? identityScope = identity is null ? null : identityAccessor.Push(identity);
        using (identityScope)
        {
            await continuation(request, responseStream, context).ConfigureAwait(false);
        }
    }

    /// <summary>Authenticates the API key and authorizes the RPC call by required scope.</summary>
    /// <typeparam name="TRequest">Request message type.</typeparam>
    /// <param name="request">Request payload.</param>
    /// <param name="context">RPC server call context.</param>
    /// <returns>Authenticated API key identity, or null if authentication is disabled.</returns>
    private async Task<ApiKeyIdentity?> AuthenticateAndAuthorizeAsync<TRequest>(
        TRequest request,
        ServerCallContext context)
        where TRequest : class
    {
        if (options.Value.Authentication.Mode == AuthenticationMode.Disabled)
        {
            return null;
        }

        string? authorizationHeader = context.RequestHeaders.GetValue("authorization");
        ApiKeyVerificationResult verificationResult = await apiKeyVerifier
            .VerifyAsync(authorizationHeader, context.CancellationToken)
            .ConfigureAwait(false);

        if (!verificationResult.Succeeded || verificationResult.Identity is null)
        {
            throw new RpcException(new Status(
                StatusCode.Unauthenticated,
                "Missing or invalid API key."));
        }

        string requiredScope = scopeResolver.ResolveRequiredScope(request);
        if (!verificationResult.Identity.Scopes.Contains(requiredScope))
        {
            throw new RpcException(new Status(
                StatusCode.PermissionDenied,
                $"API key is missing required scope '{requiredScope}'."));
        }

        return verificationResult.Identity;
    }
}
