using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ZB.MOM.WW.ScadaBridge.InboundAPI;

/// <summary>
/// Endpoint filter applied to <c>POST /api/{methodName}</c> that enforces two
/// cross-cutting guards before the request handler runs:
///
/// <list type="bullet">
/// <item><description>
/// Active-node gating. The inbound API is central-active-node-only;
/// a standby node returns HTTP 503 so it never executes method scripts.
/// </description></item>
/// <item><description>
/// Request body size cap. Oversized bodies are rejected with HTTP
/// 413 before being buffered into a <c>JsonDocument</c>.
/// </description></item>
/// </list>
/// </summary>
public sealed class InboundApiEndpointFilter : IEndpointFilter
{
    private readonly ILogger<InboundApiEndpointFilter> _logger;
    private readonly IOptionsMonitor<InboundApiOptions> _options;

    /// <summary>Initializes a new <see cref="InboundApiEndpointFilter"/> with the given logger and options.</summary>
    /// <param name="logger">Logger for request rejection diagnostics.</param>
    /// <param name="options">Live-reloadable inbound API options, read per-request.</param>
    public InboundApiEndpointFilter(
        ILogger<InboundApiEndpointFilter> logger,
        IOptionsMonitor<InboundApiOptions> options)
    {
        _logger = logger;
        _options = options;
    }

    /// <summary>Applies active-node gating and request body size checks before delegating to the next filter or handler.</summary>
    /// <param name="context">The endpoint filter invocation context containing the HTTP context and arguments.</param>
    /// <param name="next">The next filter or endpoint handler in the pipeline.</param>
    /// <returns>A value task that resolves to the filter pipeline result, or an error result if gating fails.</returns>
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var httpContext = context.HttpContext;

        // Refuse to serve the inbound API on a standby central node.
        // The gate is optional — when no IActiveNodeGate is registered (non-clustered
        // host / tests) the API is served, preserving prior behaviour.
        var gate = httpContext.RequestServices.GetService<IActiveNodeGate>();
        if (gate is { IsActiveNode: false })
        {
            _logger.LogWarning(
                "Inbound API request rejected — this node is a standby (not the active central node)");
            return Results.Json(
                new { error = "Inbound API is only available on the active central node", code = "STANDBY_NODE" },
                statusCode: StatusCodes.Status503ServiceUnavailable);
        }

        // Cap the request body size. Reject an over-limit body up
        // front via Content-Length; also lower the per-request max body size so a
        // chunked/unknown-length stream is cut off by Kestrel as it is read.
        var maxBytes = _options.CurrentValue.MaxRequestBodyBytes;
        if (httpContext.Request.ContentLength is { } declaredLength && declaredLength > maxBytes)
        {
            _logger.LogWarning(
                "Inbound API request rejected — body length {Length} exceeds limit {Limit}",
                declaredLength, maxBytes);
            return Results.Json(
                new { error = "Request body too large", code = "BODY_TOO_LARGE" },
                statusCode: StatusCodes.Status413PayloadTooLarge);
        }

        var sizeFeature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
        if (sizeFeature is { IsReadOnly: false })
        {
            sizeFeature.MaxRequestBodySize = maxBytes;
        }

        return await next(context);
    }
}
