using Grpc.Core;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Polly;
using Polly.Retry;

namespace ZB.MOM.WW.MxGateway.Client;

/// <summary>Factory and helpers for exponential-backoff retry policies on transient gRPC failures.</summary>
internal static class MxGatewayClientRetryPolicy
{
    /// <summary>Creates a Polly ResiliencePipeline that retries transient gRPC failures with exponential backoff.</summary>
    /// <param name="options">Retry configuration (max attempts, delay bounds, jitter).</param>
    /// <param name="logger">Optional logger for retry diagnostics.</param>
    /// <returns>A configured resilience pipeline that retries transient gRPC failures.</returns>
    public static ResiliencePipeline Create(
        MxGatewayClientRetryOptions options,
        ILogger? logger)
    {
        ArgumentNullException.ThrowIfNull(options);
        options.Validate();

        return new ResiliencePipelineBuilder()
            .AddRetry(new RetryStrategyOptions
            {
                MaxRetryAttempts = Math.Max(0, options.MaxAttempts - 1),
                BackoffType = DelayBackoffType.Exponential,
                UseJitter = options.UseJitter,
                Delay = options.Delay,
                MaxDelay = options.MaxDelay,
                ShouldHandle = new PredicateBuilder().Handle<Exception>(IsTransientGrpcFailure),
                OnRetry = args =>
                {
                    logger?.LogDebug(
                        args.Outcome.Exception,
                        "Retrying MXAccess Gateway client call after transient gRPC failure. Attempt {Attempt}.",
                        args.AttemptNumber + 1);
                    return default;
                },
            })
            .Build();
    }

    /// <summary>Returns whether a command kind is eligible for automatic retry on transient failures.</summary>
    /// <param name="kind">The command kind to check.</param>
    /// <returns><see langword="true"/> if the command kind may be automatically retried; otherwise, <see langword="false"/>.</returns>
    public static bool IsRetryableCommand(MxCommandKind kind)
    {
        return kind is MxCommandKind.Ping
            or MxCommandKind.GetSessionState
            or MxCommandKind.GetWorkerInfo;
    }

    private static bool IsTransientGrpcFailure(Exception exception)
    {
        return exception switch
        {
            RpcException rpcException => IsTransientStatus(rpcException.StatusCode),
            MxGatewayException { InnerException: RpcException rpcException } => IsTransientStatus(rpcException.StatusCode),
            _ => false,
        };
    }

    private static bool IsTransientStatus(StatusCode statusCode)
    {
        return statusCode is StatusCode.Unavailable
            or StatusCode.DeadlineExceeded
            or StatusCode.ResourceExhausted;
    }
}
