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

using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.InternalTesting;

#if !IIS_FUNCTIONALS
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;

#if IISEXPRESS_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
#elif NEWHANDLER_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
#elif NEWSHIM_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
#endif

#else
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
#endif

[Collection(IISSubAppSiteCollection.Name)]
public class RequestPathBaseTests : FixtureLoggedTest
{
    private readonly IISSubAppSiteFixture _fixture;

    public RequestPathBaseTests(IISSubAppSiteFixture fixture) : base(fixture)
    {
        _fixture = fixture;
    }

    [ConditionalTheory]
    [RequiresNewHandler]
    [InlineData("/Sub/App/PathAndPathBase", "/Sub/App/PathAndPathBase", "")]
    [InlineData("/SUb/APp/PathAndPAthBase", "/SUb/APp/PathAndPAthBase", "")]
    [InlineData(@"/Sub\App/PathAndPathBase/", @"/Sub\App/PathAndPathBase", "/")]
    [InlineData("/Sub%2FApp/PathAndPathBase/", "/Sub%2FApp/PathAndPathBase", "/")]
    [InlineData("/Sub%2fApp/PathAndPathBase/", "/Sub%2fApp/PathAndPathBase", "/")]
    [InlineData("/Sub%5cApp/PathAndPathBase/", @"/Sub\App/PathAndPathBase", "/")]
    [InlineData("/Sub%5CApp/PathAndPathBase/", @"/Sub\App/PathAndPathBase", "/")]
    [InlineData("/Sub/App/PathAndPathBase/Path", "/Sub/App/PathAndPathBase", "/Path")]
    [InlineData("/Sub/App/PathANDPathBase/PATH", "/Sub/App/PathANDPathBase", "/PATH")]
    public async Task RequestPathBase_Split(string url, string expectedPathBase, string expectedPath)
    {
        // The test app trims the test name off of the request path and puts it on the PathBase.
        // /AppName/TestName/Path
        var (status, body) = await SendSocketRequestAsync(url);
        Assert.Equal(200, status);
        Assert.Equal($"PathBase: {expectedPathBase}; Path: {expectedPath}", body);
    }

    [ConditionalTheory]
    [RequiresNewHandler]
    [InlineData("//Sub/App/PathAndPathBase", "//Sub/App/PathAndPathBase", "")]
    [InlineData(@"/\Sub/App/PathAndPathBase/", @"/\Sub/App/PathAndPathBase", "/")]
    [InlineData(@"/Sub/\App/PathAndPathBase//path", @"/Sub/\App/PathAndPathBase", "//path")]
    [InlineData("/%2FSub/App/PathAndPathBase/", "/%2FSub/App/PathAndPathBase", "/")]
    [InlineData("/%5CSub/App/PathAndPathBase/", @"/\Sub/App/PathAndPathBase", "/")]
    [InlineData("///Sub/App/PathAndPathBase/path1/path2", "///Sub/App/PathAndPathBase", "/path1/path2")]
    [InlineData("/Sub%2F/App/PathAndPathBase/%2FPath", "/Sub%2F/App/PathAndPathBase", "/%2FPath")]
    [InlineData(@"/%2F\/Sub/App/PathAndPathBase/Path", @"/%2F\/Sub/App/PathAndPathBase", "/Path")]
    [InlineData(@"/Sub/App/PathANDPathBase/PATH", @"/Sub/App/PathANDPathBase", "/PATH")]
    [InlineData("/Sub/%5cApp/PathAndPathBase/", @"/Sub/\App/PathAndPathBase", "/")]
    [InlineData("//Sub//App/PathAndPathBase//Path", "//Sub//App/PathAndPathBase", "//Path")]
    [InlineData(@"/Sub/ball/../App/PathAndPathBase/path1//path2", @"/Sub/App/PathAndPathBase", "/path1//path2")]
    [InlineData(@"/Sub//ball/../App/PathAndPathBase/path1//path2", @"/Sub//App/PathAndPathBase", "/path1//path2")]
    // The results should be "/Sub//App/PathAndPathBase", "//path1//path2", but Http.Sys collapses the "//" before the "../"
    // and we don't have a good way of emulating that.
    // [InlineData(@"/Sub/call//../App/PathAndPathBase//path1//path2", @"", "/Sub/call/App/PathAndPathBase//path1//path2")]
    [InlineData(@"/Sub/call/.%2e/App/PathAndPathBase//path1//path2", @"/Sub/App/PathAndPathBase", "//path1//path2")]
    [InlineData(@"/Sub/call/.%2E/App/PathAndPathBase//path1//path2", @"/Sub/App/PathAndPathBase", "//path1//path2")]
    public async Task RequestPathBase_WithDoubleSlashes_Split(string url, string expectedPathBase, string expectedPath)
    {
        // The test app trims the test name off of the request path and puts it on the PathBase.
        // /AppName/TestName/Path
        var (status, body) = await SendSocketRequestAsync(url);
        Assert.Equal(200, status);
        Assert.Equal($"PathBase: {expectedPathBase}; Path: {expectedPath}", body);
    }

    private async Task<(int Status, string Body)> SendSocketRequestAsync(string path)
    {
        using (var connection = _fixture.CreateTestConnection())
        {
            await connection.Send(
                "GET " + path + " HTTP/1.1",
                "Host: " + _fixture.Client.BaseAddress.Authority,
                "",
                "");
            var headers = await connection.ReceiveHeaders();
            var status = int.Parse(headers[0].Substring(9, 3), CultureInfo.InvariantCulture);
            if (headers.Contains("Transfer-Encoding: chunked"))
            {
                var bytes0 = await connection.ReceiveChunk();
                return (status, Encoding.UTF8.GetString(bytes0.Span));
            }
            var length = int.Parse(headers.Single(h => h.StartsWith("Content-Length: ", StringComparison.Ordinal))["Content-Length: ".Length..], CultureInfo.InvariantCulture);
            var bytes1 = await connection.Receive(length);
            return (status, Encoding.ASCII.GetString(bytes1.Span));
        }
    }
}
