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

using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests;

public class ResponseHeaderTests : TestApplicationErrorLoggerLoggedTest
{
    [Fact]
    public async Task ResponseHeaders_WithNonAscii_Throws()
    {
        await using var server = new TestServer(context =>
        {
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.ContentType = "Custom 你好 Type"); // Special cased
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Accept = "Custom 你好 Accept"); // Not special cased
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
            context.Response.ContentLength = 11;
            return context.Response.WriteAsync("Hello World");
        }, new TestServiceContext(LoggerFactory));
        using var connection = server.CreateConnection();
        await connection.Send(
            "GET / HTTP/1.1",
            "Host:",
            "",
            "");

        await connection.Receive(
            $"HTTP/1.1 200 OK",
            "Content-Length: 11",
            $"Date: {server.Context.DateHeaderValue}",
            "",
            "Hello World");
    }

    [Fact]
    public async Task ResponseHeaders_WithNonAsciiWithCustomEncoding_Works()
    {
        var testContext = new TestServiceContext(LoggerFactory);
        testContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;

        await using var server = new TestServer(context =>
        {
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
            Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
            context.Response.ContentType = "Custom 你好 Type";
            context.Response.Headers.Accept = "Custom 你好 Accept";
            context.Response.Headers.Append("CustomName", "Custom 你好 Value");
            context.Response.ContentLength = 11;
            return context.Response.WriteAsync("Hello World");
        }, testContext);

        using var connection = server.CreateConnection(Encoding.UTF8);
        await connection.Send(
            "GET / HTTP/1.1",
            "Host:",
            "",
            "");

        await connection.Receive(
            $"HTTP/1.1 200 OK",
            "Content-Length: 11",
            "Content-Type: Custom 你好 Type",
            $"Date: {server.Context.DateHeaderValue}",
            "Accept: Custom 你好 Accept",
            "CustomName: Custom 你好 Value",
            "",
            "Hello World");
    }

    [Fact]
    public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection()
    {
        var testContext = new TestServiceContext(LoggerFactory);
        var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback,
            DecoderFallback.ExceptionFallback);
        testContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding;

        await using var server = new TestServer(context =>
        {
            context.Response.Headers.Append("CustomName", "Custom 你好 Value");
            context.Response.ContentLength = 11;
            return context.Response.WriteAsync("Hello World");
        }, testContext);
        using var connection = server.CreateConnection();
        await connection.Send(
            "GET / HTTP/1.1",
            "Host:",
            "",
            "");

        await connection.ReceiveEnd();
    }

    [Fact]
    public async Task ResponseHeaders_NullEntriesAreIgnored()
    {
        var tag = "Warning";

        await using var server = new TestServer(context =>
        {
            Assert.Empty(context.Response.Headers[tag]);

            context.Response.Headers.Add(tag, new StringValues((string)null));

            Assert.Empty(context.Response.Headers[tag]);

            // this should not throw
            context.Response.Headers.Add(tag, new StringValues("Hello"));

            context.Response.ContentLength = 11;
            return context.Response.WriteAsync("Hello World");
        }, new TestServiceContext(LoggerFactory));

        using var connection = server.CreateConnection();
        await connection.Send(
            "GET / HTTP/1.1",
            "Host:",
            "",
            "");

        await connection.Receive(
            $"HTTP/1.1 200 OK",
            "Content-Length: 11",
            $"Date: {server.Context.DateHeaderValue}",
            $"{tag}: Hello",
            "",
            "Hello World");
    }
}
