using System;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Security
{
    /// <summary>
    ///     gRPC interceptor for API key authentication.
    ///     Validates API keys for incoming requests and enforces role-based access control.
    /// </summary>
    public class ApiKeyInterceptor : Interceptor
    {
        private static readonly ILogger Logger = Log.ForContext<ApiKeyInterceptor>();

        /// <summary>
        ///     List of gRPC method names that require write access.
        /// </summary>
        private static readonly string[] WriteMethodNames =
        {
            "Write",
            "WriteBatch",
            "WriteBatchAndWait"
        };

        private readonly ApiKeyService _apiKeyService;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ApiKeyInterceptor" /> class.
        /// </summary>
        /// <param name="apiKeyService">The API key service used for validation.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="apiKeyService" /> is null.</exception>
        public ApiKeyInterceptor(ApiKeyService apiKeyService)
        {
            _apiKeyService = apiKeyService ?? throw new ArgumentNullException(nameof(apiKeyService));
        }

        /// <summary>
        ///     Handles unary gRPC calls, validating API key and enforcing permissions.
        /// </summary>
        /// <typeparam name="TRequest">The request type.</typeparam>
        /// <typeparam name="TResponse">The response type.</typeparam>
        /// <param name="request">The request message.</param>
        /// <param name="context">The server call context.</param>
        /// <param name="continuation">The continuation delegate.</param>
        /// <returns>The response message.</returns>
        /// <exception cref="RpcException">Thrown if authentication or authorization fails.</exception>
        public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
            TRequest request,
            ServerCallContext context,
            UnaryServerMethod<TRequest, TResponse> continuation)
        {
            string apiKey = GetApiKeyFromContext(context);
            string methodName = GetMethodName(context.Method);

            if (string.IsNullOrEmpty(apiKey))
            {
                Logger.Warning("Missing API key for method {Method} from {Peer}",
                    context.Method, context.Peer);
                throw new RpcException(new Status(StatusCode.Unauthenticated, "API key is required"));
            }

            ApiKey? key = _apiKeyService.ValidateApiKey(apiKey);
            if (key == null)
            {
                Logger.Warning("Invalid API key for method {Method} from {Peer}",
                    context.Method, context.Peer);
                throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid API key"));
            }

            // Check if method requires write access
            if (IsWriteMethod(methodName) && key.Role != ApiKeyRole.ReadWrite)
            {
                Logger.Warning("Insufficient permissions for method {Method} with API key {Description}",
                    context.Method, key.Description);
                throw new RpcException(new Status(StatusCode.PermissionDenied,
                    "API key does not have write permissions"));
            }

            // Add API key info to context items for use in service methods
            context.UserState["ApiKey"] = key;

            Logger.Debug("Authorized method {Method} for API key {Description}",
                context.Method, key.Description);

            return await continuation(request, context);
        }

        /// <summary>
        ///     Handles server streaming gRPC calls, validating API key and enforcing permissions.
        /// </summary>
        /// <typeparam name="TRequest">The request type.</typeparam>
        /// <typeparam name="TResponse">The response type.</typeparam>
        /// <param name="request">The request message.</param>
        /// <param name="responseStream">The response stream writer.</param>
        /// <param name="context">The server call context.</param>
        /// <param name="continuation">The continuation delegate.</param>
        /// <returns>A task representing the asynchronous operation.</returns>
        /// <exception cref="RpcException">Thrown if authentication fails.</exception>
        public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
            TRequest request,
            IServerStreamWriter<TResponse> responseStream,
            ServerCallContext context,
            ServerStreamingServerMethod<TRequest, TResponse> continuation)
        {
            string apiKey = GetApiKeyFromContext(context);

            if (string.IsNullOrEmpty(apiKey))
            {
                Logger.Warning("Missing API key for streaming method {Method} from {Peer}",
                    context.Method, context.Peer);
                throw new RpcException(new Status(StatusCode.Unauthenticated, "API key is required"));
            }

            ApiKey? key = _apiKeyService.ValidateApiKey(apiKey);
            if (key == null)
            {
                Logger.Warning("Invalid API key for streaming method {Method} from {Peer}",
                    context.Method, context.Peer);
                throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid API key"));
            }

            // Add API key info to context items
            context.UserState["ApiKey"] = key;

            Logger.Debug("Authorized streaming method {Method} for API key {Description}",
                context.Method, key.Description);

            await continuation(request, responseStream, context);
        }

        /// <summary>
        ///     Extracts the API key from the gRPC request headers.
        /// </summary>
        /// <param name="context">The server call context.</param>
        /// <returns>The API key value, or an empty string if not found.</returns>
        private static string GetApiKeyFromContext(ServerCallContext context)
        {
            // Check for API key in metadata (headers)
            Metadata.Entry? entry = context.RequestHeaders.FirstOrDefault(e =>
                e.Key.Equals("x-api-key", StringComparison.OrdinalIgnoreCase));

            return entry?.Value ?? string.Empty;
        }

        /// <summary>
        ///     Gets the method name from the full gRPC method string.
        /// </summary>
        /// <param name="method">The full method string (e.g., /package.Service/Method).</param>
        /// <returns>The method name.</returns>
        private static string GetMethodName(string method)
        {
            // Method format is /package.Service/Method
            int lastSlash = method.LastIndexOf('/');
            return lastSlash >= 0 ? method.Substring(lastSlash + 1) : method;
        }

        /// <summary>
        ///     Determines whether the specified method name requires write access.
        /// </summary>
        /// <param name="methodName">The method name.</param>
        /// <returns><c>true</c> if the method requires write access; otherwise, <c>false</c>.</returns>
        private static bool IsWriteMethod(string methodName) =>
            WriteMethodNames.Contains(methodName, StringComparer.OrdinalIgnoreCase);
    }
}
