using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Primitives;
using SatelliteProvider.Api.Validators;

namespace SatelliteProvider.Tests.Validators;

// AZ-811: unit coverage for the envelope filter that runs ahead of the
// FluentValidation layer on query-string endpoints. Spec section 5 calls for
// ≥ 1 unit test on this filter; integration coverage is in
// SatelliteProvider.IntegrationTests/GetTileByLatLonValidationTests.cs.
public class RejectUnknownQueryParamsEndpointFilterTests
{
    private static readonly string[] AllowedKeys = ["lat", "lon", "zoom"];

    [Fact]
    public async Task Invoke_AllKeysAllowed_DelegatesToNext()
    {
        // Arrange
        var filter = new RejectUnknownQueryParamsEndpointFilter(AllowedKeys);
        var ctx = BuildContext(new Dictionary<string, StringValues>
        {
            ["lat"] = "47.461747",
            ["lon"] = "37.647063",
            ["zoom"] = "18"
        });
        var sentinel = new object();
        EndpointFilterDelegate next = _ => ValueTask.FromResult<object?>(sentinel);

        // Act
        var result = await filter.InvokeAsync(ctx, next);

        // Assert
        result.Should().BeSameAs(sentinel, "the filter must pass through when all query keys are in the allowed set");
    }

    [Fact]
    public async Task Invoke_UnknownKey_ReturnsValidationProblemAndDoesNotDelegate()
    {
        // Arrange
        var filter = new RejectUnknownQueryParamsEndpointFilter(AllowedKeys);
        var ctx = BuildContext(new Dictionary<string, StringValues>
        {
            ["lat"] = "47.461747",
            ["lon"] = "37.647063",
            ["zoom"] = "18",
            ["debug"] = "1"
        });
        var nextCalled = false;
        EndpointFilterDelegate next = _ =>
        {
            nextCalled = true;
            return ValueTask.FromResult<object?>(new object());
        };

        // Act
        var result = await filter.InvokeAsync(ctx, next);

        // Assert
        nextCalled.Should().BeFalse("an unknown key must short-circuit the pipeline before the handler runs");
        var problem = result.Should().BeOfType<ProblemHttpResult>().Subject;
        problem.StatusCode.Should().Be(StatusCodes.Status400BadRequest);
        problem.ProblemDetails.Should().BeOfType<HttpValidationProblemDetails>();
        var validation = (HttpValidationProblemDetails)problem.ProblemDetails;
        validation.Errors.Should().ContainKey("debug");
        validation.Errors["debug"][0].Should().Contain("Unknown query parameter");
    }

    [Fact]
    public async Task Invoke_LegacyPascalCaseKeys_ReturnsErrorsPerKey()
    {
        // Arrange — AZ-811 envelope must catch the exact pre-rename wire format
        // (`Latitude/Longitude/ZoomLevel`) because case-insensitive lookup against
        // the allowed set still treats those keys as distinct from `lat/lon/zoom`.
        var filter = new RejectUnknownQueryParamsEndpointFilter(AllowedKeys);
        var ctx = BuildContext(new Dictionary<string, StringValues>
        {
            ["Latitude"] = "47.461747",
            ["Longitude"] = "37.647063",
            ["ZoomLevel"] = "18"
        });
        EndpointFilterDelegate next = _ => ValueTask.FromResult<object?>(new object());

        // Act
        var result = await filter.InvokeAsync(ctx, next);

        // Assert
        var problem = result.Should().BeOfType<ProblemHttpResult>().Subject;
        var validation = (HttpValidationProblemDetails)problem.ProblemDetails;
        validation.Errors.Should().ContainKey("Latitude");
        validation.Errors.Should().ContainKey("Longitude");
        validation.Errors.Should().ContainKey("ZoomLevel");
    }

    [Fact]
    public async Task Invoke_KeysAreCaseInsensitiveAgainstAllowedSet()
    {
        // Arrange — `Lat` (capital L) is the SAME allowed key as `lat`
        // (`StringComparer.OrdinalIgnoreCase`). It must pass through.
        var filter = new RejectUnknownQueryParamsEndpointFilter(AllowedKeys);
        var ctx = BuildContext(new Dictionary<string, StringValues>
        {
            ["Lat"] = "47.461747",
            ["lon"] = "37.647063",
            ["ZOOM"] = "18"
        });
        var sentinel = new object();
        EndpointFilterDelegate next = _ => ValueTask.FromResult<object?>(sentinel);

        // Act
        var result = await filter.InvokeAsync(ctx, next);

        // Assert
        result.Should().BeSameAs(sentinel);
    }

    private static EndpointFilterInvocationContext BuildContext(IDictionary<string, StringValues> queryParams)
    {
        var httpContext = new DefaultHttpContext();
        httpContext.Request.Query = new QueryCollection(queryParams.ToDictionary(kv => kv.Key, kv => kv.Value));
        return new DefaultEndpointFilterInvocationContext(httpContext);
    }
}
