using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace ZB.MOM.WW.Health;

/// <summary>
/// Endpoint filter that gates a route to the active node. Resolves <see cref="IActiveNodeGate"/>
/// from request services; when it is registered and reports a standby
/// (<see cref="IActiveNodeGate.IsActiveNode"/> is <c>false</c>) the request is short-circuited with
/// HTTP 503 and a <c>Retry-After</c> header. When no gate is registered (non-clustered host / tests)
/// the request is served, preserving prior behaviour.
/// </summary>
public sealed class ActiveNodeGateEndpointFilter : IEndpointFilter
{
    /// <summary>Default <c>Retry-After</c> value (seconds) advertised on a standby 503 response.</summary>
    private const int DefaultRetryAfterSeconds = 5;

    private readonly int _retryAfterSeconds;

    /// <summary>Initializes a new <see cref="ActiveNodeGateEndpointFilter"/> using the default 5 s retry-after.</summary>
    public ActiveNodeGateEndpointFilter()
        : this(DefaultRetryAfterSeconds)
    {
    }

    /// <summary>Initializes a new <see cref="ActiveNodeGateEndpointFilter"/>.</summary>
    /// <param name="retryAfterSeconds">The <c>Retry-After</c> value (seconds) advertised on a standby 503 response.</param>
    public ActiveNodeGateEndpointFilter(int retryAfterSeconds) => _retryAfterSeconds = retryAfterSeconds;

    /// <summary>
    /// Returns 503 (with <c>Retry-After</c>) when the resolved <see cref="IActiveNodeGate"/> reports
    /// a standby node; otherwise delegates to the next filter or endpoint handler.
    /// </summary>
    /// <param name="context">The endpoint filter invocation context.</param>
    /// <param name="next">The next filter or endpoint handler in the pipeline.</param>
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(next);

        var httpContext = context.HttpContext;
        var gate = httpContext.RequestServices.GetService<IActiveNodeGate>();

        if (gate is { IsActiveNode: false })
        {
            httpContext.Response.Headers.RetryAfter =
                _retryAfterSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture);
            return Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
        }

        return await next(context);
    }
}

/// <summary>
/// Route convention that gates endpoint(s) to the active node, returning 503 on standby nodes.
/// </summary>
public static class ActiveNodeGateExtensions
{
    /// <summary>
    /// Applies <see cref="ActiveNodeGateEndpointFilter"/> to the decorated endpoint(s): the route is
    /// served only when the DI-resolved <see cref="IActiveNodeGate"/> reports the node active, and
    /// returns 503 with a <c>Retry-After</c> header when the node is a standby.
    /// </summary>
    /// <param name="builder">The endpoint convention builder to decorate.</param>
    /// <param name="retryAfterSeconds">
    /// The <c>Retry-After</c> value (seconds) advertised on a standby 503 response. Defaults to 5.
    /// </param>
    /// <returns>The same <paramref name="builder"/> for chaining.</returns>
    public static IEndpointConventionBuilder RequireActiveNode(
        this IEndpointConventionBuilder builder,
        int retryAfterSeconds = 5)
    {
        ArgumentNullException.ThrowIfNull(builder);
        return builder.AddEndpointFilter(new ActiveNodeGateEndpointFilter(retryAfterSeconds));
    }
}
