using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
 
namespace Shared.TCP
{
    public class TcpServer
    {
        private readonly ILogger<TcpServer> _logger;
        private readonly IServerConfiguration _serverConfiguration;
 
        private readonly ConcurrentDictionary<Guid, Socket> _clients = new ConcurrentDictionary<Guid, Socket>();
 
        private object _startLocker = new object();
        private bool _isStarted;
 
        public TcpServer(ILogger<TcpServer> logger, IServerConfiguration serverConfiguration)
        {
            if (logger is null)
            {
                throw new ArgumentNullException(nameof(logger));
            }
 
            if (serverConfiguration is null)
            {
                throw new ArgumentNullException(nameof(serverConfiguration));
            }
            _logger = logger;
            _serverConfiguration = serverConfiguration;
        }
 
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            lock (_startLocker)
            {
                if (_isStarted)
                {
                    _logger.LogWarning("Server alrady started");
                    return;
                }
                _isStarted = true;
            }
            Thread handleConnected = new Thread(async () =>
               {
                   try
                   {
 
                       while (!cancellationToken.IsCancellationRequested)
                       {
                           //Проверим на связи ли клиент пинганув
 
 
                           foreach(var c in _clients)
                           {
                               try
                               {
                                   await SendToClient(c.Key, Array.Empty<byte>());
 
                               }
                               catch(Exception ex)
                               {
                                   _logger.LogError(ex.ToString());
                               }
                           }
 
                           //Чистим отвалившихся
                           var disconnected = _clients.Where(x => x.Value.Connected);
                           foreach (var it in disconnected)
                           {
                               var removed = _clients.TryRemove(it.Key, out var client);
                               if (!removed)
                               {
                                   _logger.LogWarning($"Cant remove disconnected client with GUID {it.Key}");
                                   continue;
                               }
                               client.Shutdown(SocketShutdown.Both);
                               client.Close();
                           }
                           await Task.Delay(1000, cancellationToken);
                       }
                   }
                   catch (Exception ex)
                   {
                       _logger.LogDebug(ex.ToString());
                   }
               });
 
            Thread acceptClientThread = new Thread(
                async () =>
                {
                    try
                    {
                        cancellationToken.ThrowIfCancellationRequested();
 
                        TcpListener listener = new TcpListener(_serverConfiguration.ServerLicalAddress, _serverConfiguration.ServerPort);
 
                        listener.Start(_serverConfiguration.Backlog);
 
                        while (!cancellationToken.IsCancellationRequested)
                        {
                            if (_serverConfiguration.MaxConnections <= _clients.Count)
                            {
                                await Task.Delay(500);
                                continue;
                            }
 
                            var client = await listener.AcceptSocketAsync();
                            Guid clientGuid = Guid.NewGuid();
                            if (_clients.TryAdd(clientGuid, client))
                            {
                                _logger.LogInformation("Client accepted");
                            }
                        }
                    }
                    catch (OperationCanceledException)
                    {
                        _logger.LogInformation("Server stopped");
                    }
                });
            acceptClientThread.Start();
            handleConnected.Start();
        }
 
        public async Task SendToClient(Guid clientGuid, ReadOnlyMemory<byte> message, CancellationToken cancellationToken = default)
        {
            try
            {
                using var stream = new NetworkStream(_clients[clientGuid], false);
                {
                    await stream.WriteAsync(message.ToArray(), 0, message.Length);
                }
            }
            catch (SocketException sex)
            {
                _logger.LogError(sex.ToString());
            }
            catch (Exception ex)
            {
                _logger.LogDebug(ex.ToString());
            }
        }
        public async Task BrodcastMessage(ReadOnlyMemory<byte> message, CancellationToken cancellationToken = default)
        {
            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                var tasks = _clients.Where(x => x.Value.Connected).Select(x => SendToClient(x.Key, message, cancellationToken));
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.ToString());
            }
 
        }
    }
}