// 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 Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Shared;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Http;

public class DefaultHttpRequestTests
{
    [Theory]
    [InlineData(0)]
    [InlineData(9001)]
    [InlineData(65535)]
    public void GetContentLength_ReturnsParsedHeader(long value)
    {
        // Arrange
        var request = GetRequestWithContentLength(value.ToString(CultureInfo.InvariantCulture));

        // Act and Assert
        Assert.Equal(value, request.ContentLength);
    }

    [Fact]
    public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
    {
        // Arrange
        var request = GetRequestWithContentLength(contentLength: null);

        // Act and Assert
        Assert.Null(request.ContentLength);
    }

    [Theory]
    [InlineData("cant-parse-this")]
    [InlineData("-1000")]
    [InlineData("1000.00")]
    [InlineData("100/5")]
    public void GetContentLength_ReturnsNullIfHeaderCannotBeParsed(string contentLength)
    {
        // Arrange
        var request = GetRequestWithContentLength(contentLength);

        // Act and Assert
        Assert.Null(request.ContentLength);
    }

    [Fact]
    public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
    {
        // Arrange
        var request = GetRequestWithContentType(contentType: null);

        // Act and Assert
        Assert.Null(request.ContentType);
    }

    [Fact]
    public void Host_GetsHostFromHeaders()
    {
        // Arrange
        const string expected = "localhost:9001";

        var headers = new HeaderDictionary()
            {
                { "Host", expected },
            };

        var request = CreateRequest(headers);

        // Act
        var host = request.Host;

        // Assert
        Assert.Equal(expected, host.Value);
    }

    [Fact]
    public void Host_DecodesPunyCode()
    {
        // Arrange
        const string expected = "löcalhöst";

        var headers = new HeaderDictionary()
            {
                { "Host", "xn--lcalhst-90ae" },
            };

        var request = CreateRequest(headers);

        // Act
        var host = request.Host;

        // Assert
        Assert.Equal(expected, host.Value);
    }

    [Fact]
    public void Host_EncodesPunyCode()
    {
        // Arrange
        const string expected = "xn--lcalhst-90ae";

        var headers = new HeaderDictionary();

        var request = CreateRequest(headers);

        // Act
        request.Host = new HostString("löcalhöst");

        // Assert
        Assert.Equal(expected, headers["Host"][0]);
    }

    [Fact]
    public void IsHttps_CorrectlyReflectsScheme()
    {
        var request = new DefaultHttpContext().Request;
        Assert.Equal(string.Empty, request.Scheme);
        Assert.False(request.IsHttps);
        request.IsHttps = true;
        Assert.Equal("https", request.Scheme);
        request.IsHttps = false;
        Assert.Equal("http", request.Scheme);
        request.Scheme = "ftp";
        Assert.False(request.IsHttps);
        request.Scheme = "HTTPS";
        Assert.True(request.IsHttps);
    }

    [Fact]
    public void Query_GetAndSet()
    {
        var request = new DefaultHttpContext().Request;
        var requestFeature = request.HttpContext.Features.Get<IHttpRequestFeature>();
        Assert.Equal(string.Empty, requestFeature.QueryString);
        Assert.Equal(QueryString.Empty, request.QueryString);
        var query0 = request.Query;
        Assert.NotNull(query0);
        Assert.Equal(0, query0.Count);

        requestFeature.QueryString = "?name0=value0&name1=value1";
        var query1 = request.Query;
        Assert.NotSame(query0, query1);
        Assert.Equal(2, query1.Count);
        Assert.Equal("value0", query1["name0"]);
        Assert.Equal("value1", query1["name1"]);

        var query2 = new QueryCollection(new Dictionary<string, StringValues>()
            {
                { "name2", "value2" }
            });

        request.Query = query2;
        Assert.Same(query2, request.Query);
        Assert.Equal("?name2=value2", requestFeature.QueryString);
        Assert.Equal(new QueryString("?name2=value2"), request.QueryString);
    }

    [Fact]
    public void Cookies_GetAndSet()
    {
        var request = new DefaultHttpContext().Request;
        var cookieHeaders = request.Headers["Cookie"];
        Assert.Empty(cookieHeaders);
        var cookies0 = request.Cookies;
        Assert.Empty(cookies0);
        Assert.Null(cookies0["key0"]);
        Assert.False(cookies0.ContainsKey("key0"));

        var newCookies = new[] { "name0=value0%2C", "name1=value1" };
        request.Headers["Cookie"] = newCookies;

        cookies0 = RequestCookieCollection.Parse(newCookies);
        var cookies1 = request.Cookies;
        Assert.Equal(cookies0, cookies1);
        Assert.Equal(2, cookies1.Count);
        Assert.Equal("value0,", cookies1["name0"]);
        Assert.Equal("value1", cookies1["name1"]);
        Assert.Equal(newCookies, request.Headers["Cookie"]);

        var cookies2 = new RequestCookieCollection(new Dictionary<string, string>()
            {
                { "name2", "value2" }
            });
        request.Cookies = cookies2;
        Assert.Equal(cookies2, request.Cookies);
        Assert.Equal("value2", request.Cookies["name2"]);
        cookieHeaders = request.Headers["Cookie"];
        Assert.Equal(new[] { "name2=value2" }, cookieHeaders);
    }

    [Fact]
    public void RouteValues_GetAndSet()
    {
        var context = new DefaultHttpContext();
        var request = context.Request;

        var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
        // No feature set for initial DefaultHttpRequest
        Assert.Null(routeValuesFeature);

        // Route values returns empty collection by default
        Assert.Empty(request.RouteValues);

        // Get and set value on request route values
        request.RouteValues["new"] = "setvalue";
        Assert.Equal("setvalue", request.RouteValues["new"]);

        routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
        // Accessing DefaultHttpRequest.RouteValues creates feature
        Assert.NotNull(routeValuesFeature);

        request.RouteValues = new RouteValueDictionary(new { key = "value" });
        // Can set DefaultHttpRequest.RouteValues
        Assert.NotNull(request.RouteValues);
        Assert.Equal("value", request.RouteValues["key"]);

        // DefaultHttpRequest.RouteValues uses feature
        Assert.Equal(routeValuesFeature.RouteValues, request.RouteValues);

        // Setting route values to null sets empty collection on request
        routeValuesFeature.RouteValues = null;
        Assert.Empty(request.RouteValues);

        var customRouteValuesFeature = new CustomRouteValuesFeature
        {
            RouteValues = new RouteValueDictionary(new { key = "customvalue" })
        };
        context.Features.Set<IRouteValuesFeature>(customRouteValuesFeature);
        // Can override DefaultHttpRequest.RouteValues with custom feature
        Assert.Equal(customRouteValuesFeature.RouteValues, request.RouteValues);

        // Can clear feature
        context.Features.Set<IRouteValuesFeature>(null);
        Assert.Empty(request.RouteValues);
    }

    [Fact]
    public void BodyReader_CanGet()
    {
        var context = new DefaultHttpContext();
        var bodyPipe = context.Request.BodyReader;
        Assert.NotNull(bodyPipe);
    }

    [Fact]
    public void DebuggerToString_EmptyRequest()
    {
        var context = new DefaultHttpContext();

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("(unspecified)", debugText);
    }

    [Fact]
    public void DebuggerToString_HasMethod()
    {
        var context = new DefaultHttpContext();
        context.Request.Method = "GET";

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("GET (unspecified)", debugText);
    }

    [Fact]
    public void DebuggerToString_HasProtocol()
    {
        var context = new DefaultHttpContext();
        context.Request.Protocol = "HTTP/2";

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("(unspecified) HTTP/2", debugText);
    }

    [Fact]
    public void DebuggerToString_HasContentType()
    {
        var context = new DefaultHttpContext();
        context.Request.ContentType = "application/json";

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("(unspecified) application/json", debugText);
    }

    [Fact]
    public void DebuggerToString_HasQueryString()
    {
        var context = new DefaultHttpContext();
        context.Request.QueryString = new QueryString("?query=true");

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("(unspecified)://(unspecified)?query=true", debugText);
    }

    [Fact]
    public void DebuggerToString_HasCompleteRequestUri()
    {
        var context = new DefaultHttpContext();
        context.Request.Scheme = "http";
        context.Request.Host = new HostString("localhost", 8080);
        context.Request.Path = "/Path";
        context.Request.PathBase = "/PathBase";
        context.Request.QueryString = new QueryString("?test=true");

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("http://localhost:8080/PathBase/Path?test=true", debugText);
    }

    [Fact]
    public void DebuggerToString_HasPartialRequestUri()
    {
        var context = new DefaultHttpContext();
        context.Request.Scheme = "http";
        context.Request.Host = new HostString("localhost");

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("http://localhost", debugText);
    }

    [Fact]
    public void DebuggerToString_HasEverything()
    {
        var context = new DefaultHttpContext();
        context.Request.Method = "GET";
        context.Request.Protocol = "HTTP/2";
        context.Request.ContentType = "application/json";
        context.Request.Scheme = "http";
        context.Request.Host = new HostString("localhost", 8080);
        context.Request.Path = "/Path";
        context.Request.PathBase = "/PathBase";
        context.Request.QueryString = new QueryString("?test=true");

        var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
        Assert.Equal("GET http://localhost:8080/PathBase/Path?test=true HTTP/2 application/json", debugText);
    }

    private class CustomRouteValuesFeature : IRouteValuesFeature
    {
        public RouteValueDictionary RouteValues { get; set; }
    }

    private static HttpRequest CreateRequest(IHeaderDictionary headers)
    {
        var context = new DefaultHttpContext();
        context.Features.Get<IHttpRequestFeature>().Headers = headers;
        return context.Request;
    }

    private static HttpRequest GetRequestWithContentLength(string contentLength = null)
    {
        return GetRequestWithHeader("Content-Length", contentLength);
    }

    private static HttpRequest GetRequestWithContentType(string contentType = null)
    {
        return GetRequestWithHeader("Content-Type", contentType);
    }

    private static HttpRequest GetRequestWithAcceptHeader(string acceptHeader = null)
    {
        return GetRequestWithHeader("Accept", acceptHeader);
    }

    private static HttpRequest GetRequestWithAcceptCharsetHeader(string acceptCharset = null)
    {
        return GetRequestWithHeader("Accept-Charset", acceptCharset);
    }

    private static HttpRequest GetRequestWithHeader(string headerName, string headerValue)
    {
        var headers = new HeaderDictionary();
        if (headerValue != null)
        {
            headers.Add(headerName, headerValue);
        }

        return CreateRequest(headers);
    }
}
