// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Routing.Matching;

public abstract partial class MatcherConformanceTest
{
    [Fact]
    public virtual async Task Match_EmptyRoute()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/");
        var httpContext = CreateContext("/");

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    [Fact]
    public virtual async Task Match_SingleLiteralSegment()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/simple");
        var httpContext = CreateContext("/simple");

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    [Fact]
    public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/simple");
        var httpContext = CreateContext("/simple/");

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    [Theory]
    [InlineData("/simple")]
    [InlineData("/sImpLe")]
    [InlineData("/SIMPLE")]
    public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/Simple");
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    // Some matchers will optimize for the ASCII case
    [Theory]
    [InlineData("/SÏmple", "/SÏmple")]
    [InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
    public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    // Matchers should operate on the decoded representation - a matcher that calls
    // `httpContext.Request.Path.ToString()` will break this test.
    [Theory]
    [InlineData("/S%mple", "/S%mple")]
    [InlineData("/S\\imple", "/S\\imple")] // surrogate pair
    public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/imple")]
    [InlineData("/siple")]
    [InlineData("/simple1")]
    [InlineData("/simple/not-simple")]
    [InlineData("/simple/a/b/c")]
    public virtual async Task NotMatch_SingleLiteralSegment(string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/simple");
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertNotMatch(httpContext);
    }

    [Theory]
    [InlineData("simple")]
    [InlineData("/simple")]
    [InlineData("~/simple")]
    public virtual async Task Match_Sanitizies_Template(string template)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext("/simple");

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    // Matchers do their own 'splitting' of the path into segments, so including
    // some extra variation here
    [Theory]
    [InlineData("/a/b", "/a/b")]
    [InlineData("/a/b", "/A/B")]
    [InlineData("/a/b", "/a/b/")]
    [InlineData("/a/b/c", "/a/b/c")]
    [InlineData("/a/b/c", "/a/b/c/")]
    [InlineData("/a/b/c/d", "/a/b/c/d")]
    [InlineData("/a/b/c/d", "/a/b/c/d/")]
    public virtual async Task Match_MultipleLiteralSegments(string template, string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint);
    }

    // Matchers do their own 'splitting' of the path into segments, so including
    // some extra variation here
    [Theory]
    [InlineData("/a/b", "/")]
    [InlineData("/a/b", "/a")]
    [InlineData("/a/b", "/a/")]
    [InlineData("/a/b", "/a//")]
    [InlineData("/a/b", "/aa/")]
    [InlineData("/a/b", "/a/bb")]
    [InlineData("/a/b", "/a/bb/")]
    [InlineData("/a/b/c", "/aa/b/c")]
    [InlineData("/a/b/c", "/a/bb/c/")]
    [InlineData("/a/b/c", "/a/b/cab")]
    [InlineData("/a/b/c", "/d/b/c/")]
    [InlineData("/a/b/c", "//b/c")]
    [InlineData("/a/b/c", "/a/b//")]
    [InlineData("/a/b/c", "/a/b/c/d")]
    [InlineData("/a/b/c", "/a/b/c/d/e")]
    public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertNotMatch(httpContext);
    }

    [Fact]
    public virtual async Task Match_SingleParameter()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/{p}");
        var httpContext = CreateContext("/14");
        var values = new RouteValueDictionary(new { p = "14", });

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint, values);
    }

    [Fact]
    public virtual async Task Match_Constraint()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/{p:int}");
        var httpContext = CreateContext("/14");
        var values = new RouteValueDictionary(new { p = "14", });

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint, values);
    }

    [Fact]
    public virtual async Task Match_SingleParameter_TrailingSlash()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/{p}");
        var httpContext = CreateContext("/14/");
        var values = new RouteValueDictionary(new { p = "14", });

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint, values);
    }

    [Fact]
    public virtual async Task Match_SingleParameter_WeirdNames()
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/foo/{ }/{.!$%}/{dynamic.data}");
        var httpContext = CreateContext("/foo/space/weirdmatch/matcherid");
        var values = new RouteValueDictionary()
            {
                { " ", "space" },
                { ".!$%", "weirdmatch" },
                { "dynamic.data", "matcherid" },
            };

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint, values);
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/a/b")]
    [InlineData("/a/b/c")]
    [InlineData("//")]
    public virtual async Task NotMatch_SingleParameter(string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher("/{p}");
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertNotMatch(httpContext);
    }

    [Theory]
    [InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] { "54", })]
    [InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
    [InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
    [InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
    [InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
    [InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
    [InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
    public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
    }

    [Theory]
    [InlineData("/{a}/b", "/54/bb")]
    [InlineData("/{a}/b", "/54/b/17")]
    [InlineData("/{a}/b", "/54/b//")]
    [InlineData("/{a}/{b}", "//73")]
    [InlineData("/{a}/{b}", "/54//")]
    [InlineData("/{a}/{b}", "/54/73/18")]
    [InlineData("/a/{b}/c", "/aa/b/c")]
    [InlineData("/a/{b}/c", "/a/b/cc")]
    [InlineData("/a/{b}/c", "/a/b/c/d")]
    [InlineData("/{a}/b/{c}", "/54/bb/c")]
    [InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
    [InlineData("/{a}/{b}/{c}", "/54/b/c//")]
    [InlineData("/{a}/{b}/{c}", "//b/c/")]
    [InlineData("/{a}/{b}/{c}", "/54//c/")]
    [InlineData("/{a}/{b}/{c}", "/54/b//")]
    public virtual async Task NotMatch_MultipleParameters(string template, string path)
    {
        // Arrange
        var (matcher, endpoint) = CreateMatcher(template);
        var httpContext = CreateContext(path);

        // Act
        await matcher.MatchAsync(httpContext);

        // Assert
        MatcherAssert.AssertNotMatch(httpContext);
    }
}
