using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using GrpcStatus = Grpc.Core.Status;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Security
{
    /// <summary>
    ///     gRPC server interceptor that enforces API key authentication and role-based authorization.
    ///     Extracts x-api-key from metadata, validates via ApiKeyService, enforces ReadWrite for writes.
    /// </summary>
    public class ApiKeyInterceptor : Interceptor
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<ApiKeyInterceptor>();

        private readonly ApiKeyService _apiKeyService;

        /// <summary>RPC method names that require the ReadWrite role.</summary>
        private static readonly HashSet<string> WriteProtectedMethods = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "/scada.ScadaService/Write",
            "/scada.ScadaService/WriteBatch",
            "/scada.ScadaService/WriteBatchAndWait"
        };

        public ApiKeyInterceptor(ApiKeyService apiKeyService)
        {
            _apiKeyService = apiKeyService;
        }

        public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
            TRequest request,
            ServerCallContext context,
            UnaryServerMethod<TRequest, TResponse> continuation)
        {
            ValidateApiKey(context);
            return await continuation(request, context);
        }

        public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
            TRequest request,
            IServerStreamWriter<TResponse> responseStream,
            ServerCallContext context,
            ServerStreamingServerMethod<TRequest, TResponse> continuation)
        {
            ValidateApiKey(context);
            await continuation(request, responseStream, context);
        }

        private void ValidateApiKey(ServerCallContext context)
        {
            // Extract x-api-key from metadata
            var apiKeyEntry = context.RequestHeaders.Get("x-api-key");
            var apiKey = apiKeyEntry?.Value ?? string.Empty;

            if (string.IsNullOrEmpty(apiKey))
            {
                Log.Warning("Request rejected: missing x-api-key header for {Method}", context.Method);
                throw new RpcException(new GrpcStatus(StatusCode.Unauthenticated, "Missing x-api-key header"));
            }

            var key = _apiKeyService.ValidateApiKey(apiKey);
            if (key == null)
            {
                Log.Warning("Request rejected: invalid API key for {Method}", context.Method);
                throw new RpcException(new GrpcStatus(StatusCode.Unauthenticated, "Invalid API key"));
            }

            // Check write authorization
            if (WriteProtectedMethods.Contains(context.Method) && key.Role != ApiKeyRole.ReadWrite)
            {
                Log.Warning("Request rejected: ReadOnly key attempted write operation {Method}", context.Method);
                throw new RpcException(new GrpcStatus(StatusCode.PermissionDenied,
                    "Write operations require a ReadWrite API key"));
            }

            // Store the validated key in UserState for downstream use
            context.UserState["ApiKey"] = key;
        }
    }
}
