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

using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Hosting;

namespace Microsoft.AspNetCore.TestHost;

public class ResponseResetTests
{
    [Fact]
    // Reset is only present for HTTP/2
    public async Task ResetFeature_Http11_Missing()
    {
        using var host = await CreateHost(httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            Assert.Null(feature);
            return Task.CompletedTask;
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version11;
        var response = await client.GetAsync("/");
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task ResetFeature_Http2_Present()
    {
        using var host = await CreateHost(httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            Assert.NotNull(feature);
            return Task.CompletedTask;
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version20;
        var response = await client.GetAsync("/");
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task ResetFeature_Http3_Present()
    {
        using var host = await CreateHost(httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            Assert.NotNull(feature);
            return Task.CompletedTask;
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version30;
        var response = await client.GetAsync("/");
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task ResetFeature_Reset_TriggersRequestAbortedToken()
    {
        var requestAborted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        using var host = await CreateHost(async httpContext =>
        {
            httpContext.RequestAborted.Register(() => requestAborted.SetResult());

            var feature = httpContext.Features.Get<IHttpResetFeature>();
            feature.Reset(12345);
            await requestAborted.Task.DefaultTimeout();
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version20;
        var rex = await Assert.ThrowsAsync<HttpResetTestException>(() => client.GetAsync("/"));
        Assert.Equal("The application reset the request with error code 12345.", rex.Message);
        Assert.Equal(12345, rex.ErrorCode);
        await requestAborted.Task.DefaultTimeout();
    }

    [Fact]
    public async Task ResetFeature_ResetBeforeHeadersSent_ClientThrows()
    {
        var resetReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        using var host = await CreateHost(async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            feature.Reset(12345);
            await resetReceived.Task.DefaultTimeout();
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version20;
        var rex = await Assert.ThrowsAsync<HttpResetTestException>(() => client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead));
        Assert.Equal("The application reset the request with error code 12345.", rex.Message);
        Assert.Equal(12345, rex.ErrorCode);
        resetReceived.SetResult();
    }

    [Fact]
    public async Task ResetFeature_ResetAfterHeadersSent_ClientBodyThrows()
    {
        var responseReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var resetReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.Body.FlushAsync();
            await responseReceived.Task.DefaultTimeout();
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            feature.Reset(12345);
            await resetReceived.Task.DefaultTimeout();
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version20;
        var response = await client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead);
        responseReceived.SetResult();
        response.EnsureSuccessStatusCode();
        var ex = await Assert.ThrowsAsync<HttpRequestException>(() => response.Content.ReadAsByteArrayAsync());
        var rex = Assert.IsAssignableFrom<HttpResetTestException>(ex.GetBaseException());
        Assert.Equal("The application reset the request with error code 12345.", rex.Message);
        Assert.Equal(12345, rex.ErrorCode);
        resetReceived.SetResult();
    }

    [Fact]
    public async Task ResetFeature_ResetAfterSomeDataSent_ClientBodyThrows()
    {
        var responseReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var resetReceived = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        using var host = await CreateHost(async httpContext =>
        {
            await httpContext.Response.WriteAsync("Hello World");
            await responseReceived.Task.DefaultTimeout();
            var feature = httpContext.Features.Get<IHttpResetFeature>();
            feature.Reset(12345);
            await resetReceived.Task.DefaultTimeout();
        });

        var client = host.GetTestServer().CreateClient();
        client.DefaultRequestVersion = HttpVersion.Version20;
        var response = await client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead);
        responseReceived.SetResult();
        response.EnsureSuccessStatusCode();
        var ex = await Assert.ThrowsAsync<HttpRequestException>(() => response.Content.ReadAsByteArrayAsync());
        var rex = Assert.IsAssignableFrom<HttpResetTestException>(ex.GetBaseException());
        Assert.Equal("The application reset the request with error code 12345.", rex.Message);
        Assert.Equal(12345, rex.ErrorCode);
        resetReceived.SetResult();
    }

    // TODO: Reset after CompleteAsync - Not sure how to surface this. CompleteAsync hasn't been implemented yet anyways.

    private Task<IHost> CreateHost(RequestDelegate appDelegate)
    {
        return new HostBuilder()
            .ConfigureWebHost(webBuilder =>
            {
                webBuilder
                    .UseTestServer()
                    .Configure(app =>
                    {
                        app.Run(appDelegate);
                    });
            })
            .StartAsync();
    }
}
