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

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Hosting;

namespace Microsoft.AspNetCore.TestHost.Tests;

public class WebSocketClientTests
{
    [Theory]
    [InlineData("http://localhost/connect", "localhost")]
    [InlineData("http://localhost:80/connect", "localhost")]
    [InlineData("http://localhost:81/connect", "localhost:81")]
    public async Task ConnectAsync_ShouldSetRequestProperties(string requestUri, string expectedHost)
    {
        string capturedScheme = null;
        string capturedHost = null;
        string capturedPath = null;

        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .UseTestServer()
                    .Configure(app =>
                    {
                        app.Run(ctx =>
                        {
                            if (ctx.Request.Path.StartsWithSegments("/connect"))
                            {
                                capturedScheme = ctx.Request.Scheme;
                                capturedHost = ctx.Request.Host.Value;
                                capturedPath = ctx.Request.Path;
                            }
                            return Task.FromResult(0);
                        });
                    });
            })
            .Build();

        await host.StartAsync();

        var testServer = host.GetTestServer();
        var client = testServer.CreateWebSocketClient();

            try
            {
                await client.ConnectAsync(
                    uri: new Uri(requestUri),
                    cancellationToken: default);
            }
            catch
            {
                // An exception will be thrown because our dummy endpoint does not implement a full Web socket server
            }

        Assert.Equal("http", capturedScheme);
        Assert.Equal(expectedHost, capturedHost);
        Assert.Equal("/connect", capturedPath);
    }

    [Fact]
    public async Task CanAcceptWebSocket()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .UseTestServer()
                    .Configure(app =>
                    {
                        app.UseWebSockets();
                        app.Run(async ctx =>
                        {
                            if (ctx.Request.Path.StartsWithSegments("/connect"))
                            {
                                if (ctx.WebSockets.IsWebSocketRequest)
                                {
                                    using var websocket = await ctx.WebSockets.AcceptWebSocketAsync();
                                    var buffer = new byte[1000];
                                    var res = await websocket.ReceiveAsync(buffer, default);
                                    await websocket.SendAsync(buffer.AsMemory(0, res.Count), System.Net.WebSockets.WebSocketMessageType.Binary, true, default);
                                    await websocket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, null, default);
                                }
                            }
                        });
                    });
            })
            .Build();

        await host.StartAsync();

        var testServer = host.GetTestServer();
        var client = testServer.CreateWebSocketClient();

        using var socket = await client.ConnectAsync(
            uri: new Uri("http://localhost/connect"),
            cancellationToken: default);

        await socket.SendAsync(new byte[10], System.Net.WebSockets.WebSocketMessageType.Binary, true, default);
        var res = await socket.ReceiveAsync(new byte[100], default);
        Assert.Equal(10, res.Count);
        Assert.True(res.EndOfMessage);

        await socket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, null, default);
    }

    [Fact]
    public async Task VerifyWebSocketAndUpgradeFeatures()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .UseTestServer()
                    .Configure(app =>
                    {
                        app.Run(async c =>
                        {
                            var upgradeFeature = c.Features.Get<IHttpUpgradeFeature>();
                            Assert.NotNull(upgradeFeature);
                            Assert.False(upgradeFeature.IsUpgradableRequest);
                            await Assert.ThrowsAsync<NotSupportedException>(() => upgradeFeature.UpgradeAsync());

                            var webSocketFeature = c.Features.Get<IHttpWebSocketFeature>();
                            Assert.NotNull(webSocketFeature);
                            Assert.True(webSocketFeature.IsWebSocketRequest);
                        });
                    });
            })
            .Build();

        await host.StartAsync();

        var testServer = host.GetTestServer();
        var client = testServer.CreateWebSocketClient();

        try
        {
            using var socket = await client.ConnectAsync(
                uri: new Uri("http://localhost/connect"),
                cancellationToken: default);
        }
        catch
        {
            // An exception will be thrown because our endpoint does not accept the websocket
        }
    }
}
