// 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.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Internal;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.StackExchangeRedis;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
{
    public class RedisHubLifetimeManagerBenchmark
    {
        private RedisHubLifetimeManager<TestHub> _manager1;
        private RedisHubLifetimeManager<TestHub> _manager2;
        private TestClient[] _clients;
        private object[] _args;
        private readonly List<string> _excludedConnectionIds = new List<string>();
        private readonly List<string> _sendIds = new List<string>();
        private readonly List<string> _groups = new List<string>();
        private readonly List<string> _users = new List<string>();

        private const int ClientCount = 20;

        [Params(2, 20)]
        public int ProtocolCount { get; set; }

        // Re-enable micro-benchmark when https://github.com/aspnet/SignalR/issues/3088 is fixed
        // [GlobalSetup]
        public void GlobalSetup()
        {
            var server = new TestRedisServer();
            var logger = NullLogger<RedisHubLifetimeManager<TestHub>>.Instance;
            var protocols = GenerateProtocols(ProtocolCount).ToArray();
            var options = Options.Create(new RedisOptions()
            {
                ConnectionFactory = _ => Task.FromResult<IConnectionMultiplexer>(new TestConnectionMultiplexer(server))
            });
            var resolver = new DefaultHubProtocolResolver(protocols, NullLogger<DefaultHubProtocolResolver>.Instance);

            _manager1 = new RedisHubLifetimeManager<TestHub>(logger, options, resolver);
            _manager2 = new RedisHubLifetimeManager<TestHub>(logger, options, resolver);

            async Task ConnectClient(TestClient client, IHubProtocol protocol, string userId, string groupName)
            {
                await _manager2.OnConnectedAsync(HubConnectionContextUtils.Create(client.Connection, protocol, userId));
                await _manager2.AddToGroupAsync(client.Connection.ConnectionId, "Everyone");
                await _manager2.AddToGroupAsync(client.Connection.ConnectionId, groupName);
            }

            // Connect clients
            _clients = new TestClient[ClientCount];
            var tasks = new Task[ClientCount];
            for (var i = 0; i < _clients.Length; i++)
            {
                var protocol = protocols[i % ProtocolCount];
                _clients[i] = new TestClient(protocol: protocol);

                string group;
                string user;
                if ((i % 2) == 0)
                {
                    group = "Evens";
                    user = "EvenUser";
                    _excludedConnectionIds.Add(_clients[i].Connection.ConnectionId);
                }
                else
                {
                    group = "Odds";
                    user = "OddUser";
                    _sendIds.Add(_clients[i].Connection.ConnectionId);
                }

                tasks[i] = ConnectClient(_clients[i], protocol, user, group);
                _ = ConsumeAsync(_clients[i]);
            }

            Task.WaitAll(tasks);

            _groups.Add("Evens");
            _groups.Add("Odds");
            _users.Add("EvenUser");
            _users.Add("OddUser");

            _args = new object[] { "Foo" };
        }

        private IEnumerable<IHubProtocol> GenerateProtocols(int protocolCount)
        {
            for (var i = 0; i < protocolCount; i++)
            {
                yield return ((i % 2) == 0)
                    ? new WrappedHubProtocol($"json_{i}", new NewtonsoftJsonHubProtocol())
                    : new WrappedHubProtocol($"messagepack_{i}", new MessagePackHubProtocol());
            }
        }

        private async Task ConsumeAsync(TestClient testClient)
        {
            while (await testClient.ReadAsync() != null)
            {
                // Just dump the message
            }
        }

        //[Benchmark]
        public async Task SendAll()
        {
            await _manager1.SendAllAsync("Test", _args);
        }

        //[Benchmark]
        public async Task SendGroup()
        {
            await _manager1.SendGroupAsync("Everyone", "Test", _args);
        }

        //[Benchmark]
        public async Task SendUser()
        {
            await _manager1.SendUserAsync("EvenUser", "Test", _args);
        }

        //[Benchmark]
        public async Task SendConnection()
        {
            await _manager1.SendConnectionAsync(_clients[0].Connection.ConnectionId, "Test", _args);
        }

        //[Benchmark]
        public async Task SendConnections()
        {
            await _manager1.SendConnectionsAsync(_sendIds, "Test", _args);
        }

        //[Benchmark]
        public async Task SendAllExcept()
        {
            await _manager1.SendAllExceptAsync("Test", _args, _excludedConnectionIds);
        }

        //[Benchmark]
        public async Task SendGroupExcept()
        {
            await _manager1.SendGroupExceptAsync("Everyone", "Test", _args, _excludedConnectionIds);
        }

        //[Benchmark]
        public async Task SendGroups()
        {
            await _manager1.SendGroupsAsync(_groups, "Test", _args);
        }

        //[Benchmark]
        public async Task SendUsers()
        {
            await _manager1.SendUsersAsync(_users, "Test", _args);
        }

        public class TestHub : Hub
        {
        }

        private class WrappedHubProtocol : IHubProtocol
        {
            private readonly string _name;
            private readonly IHubProtocol _innerProtocol;

            public string Name => _name;

            public int Version => _innerProtocol.Version;

            public TransferFormat TransferFormat => _innerProtocol.TransferFormat;

            public WrappedHubProtocol(string name, IHubProtocol innerProtocol)
            {
                _name = name;
                _innerProtocol = innerProtocol;
            }

            public bool TryParseMessage(ref ReadOnlySequence<byte> input, IInvocationBinder binder, out HubMessage message)
            {
                return _innerProtocol.TryParseMessage(ref input, binder, out message);
            }

            public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
            {
                _innerProtocol.WriteMessage(message, output);
            }

            public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
            {
                return HubProtocolExtensions.GetMessageBytes(this, message);
            }

            public bool IsVersionSupported(int version)
            {
                return _innerProtocol.IsVersionSupported(version);
            }
        }
    }
}
