// 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.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Xunit;

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 testServer = new TestServer(new WebHostBuilder()
                .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);
                    });
                })))
            {
                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 testServer = new TestServer(new WebHostBuilder()
                .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);
                            }
                        }
                    });
                })))
            {
                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 testServer = new TestServer(new WebHostBuilder()
                .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);
                    });
                })))
            {
                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
                }
            }
        }
    }
}
