using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace StandardScene.MagCarSimulator
{
    public sealed class MagCarSimTcpServer : IDisposable
    {
        private readonly IPAddress _address;
        private readonly int _port;
        private readonly Func<byte[], byte[]> _handler;
        private readonly object _clientsLock = new object();
        private readonly HashSet<TcpClient> _clients = new HashSet<TcpClient>();
        private TcpListener _listener;
        private CancellationTokenSource _cts;
        private int _clientCount;
        private bool _running;

        public MagCarSimTcpServer(IPAddress address, int port, Func<byte[], byte[]> handler)
        {
            _address = address ?? IPAddress.Any;
            _port = port;
            _handler = handler ?? throw new ArgumentNullException(nameof(handler));
        }

        public bool IsRunning => _running;
        public int ClientCount => Volatile.Read(ref _clientCount);

        public void Start()
        {
            if (_running)
            {
                return;
            }

            _cts = new CancellationTokenSource();
            _listener = new TcpListener(_address, _port);
            _listener.Server.NoDelay = true;
            _listener.Start();
            _running = true;
            _ = Task.Run(() => AcceptLoop(_cts.Token));
        }

        public void Dispose()
        {
            _running = false;
            try
            {
                _cts?.Cancel();
            }
            catch
            {
            }

            try
            {
                _listener?.Stop();
            }
            catch
            {
            }

            List<TcpClient> clients;
            lock (_clientsLock)
            {
                clients = new List<TcpClient>(_clients);
                _clients.Clear();
            }

            foreach (var client in clients)
            {
                try
                {
                    client.Close();
                }
                catch
                {
                }
            }

            _cts?.Dispose();
            _cts = null;
            _listener = null;
        }

        private async Task AcceptLoop(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                TcpClient client;
                try
                {
                    client = await _listener.AcceptTcpClientAsync(token).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    return;
                }
                catch (ObjectDisposedException)
                {
                    return;
                }
                catch (SocketException)
                {
                    if (token.IsCancellationRequested)
                    {
                        return;
                    }

                    continue;
                }

                lock (_clientsLock)
                {
                    _clients.Add(client);
                }

                _ = Task.Run(() => HandleClient(client, token), token);
            }
        }

        private void HandleClient(TcpClient client, CancellationToken token)
        {
            Interlocked.Increment(ref _clientCount);
            try
            {
                client.NoDelay = true;
                client.ReceiveTimeout = 0;
                using (var stream = client.GetStream())
                {
                    var buffer = new byte[MagCarSimProtocol.FrameLength];
                    while (!token.IsCancellationRequested)
                    {
                        if (!ReadExact(stream, buffer, token))
                        {
                            return;
                        }

                        byte[] response;
                        try
                        {
                            response = _handler(buffer);
                        }
                        catch (Exception ex)
                        {
                            MagCarSimLog.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] TCP 处理异常: {ex.Message}");
                            return;
                        }

                        if (response == null || response.Length != MagCarSimProtocol.FrameLength)
                        {
                            continue;
                        }

                        stream.Write(response, 0, response.Length);
                        stream.Flush();
                    }
                }
            }
            catch (Exception ex) when (IsBenign(ex))
            {
            }
            catch (Exception ex)
            {
                MagCarSimLog.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] TCP 连接异常: {ex.Message}");
            }
            finally
            {
                lock (_clientsLock)
                {
                    _clients.Remove(client);
                }

                try
                {
                    client.Dispose();
                }
                catch
                {
                }

                Interlocked.Decrement(ref _clientCount);
            }
        }

        private static bool ReadExact(NetworkStream stream, byte[] buffer, CancellationToken token)
        {
            var offset = 0;
            while (offset < buffer.Length)
            {
                token.ThrowIfCancellationRequested();
                int read;
                try
                {
                    read = stream.Read(buffer, offset, buffer.Length - offset);
                }
                catch (Exception ex) when (IsBenign(ex))
                {
                    return false;
                }

                if (read == 0)
                {
                    return false;
                }

                offset += read;
            }

            return true;
        }

        private static bool IsBenign(Exception ex)
        {
            for (var current = ex; current != null; current = current.InnerException)
            {
                if (current is ObjectDisposedException || current is OperationCanceledException)
                {
                    return true;
                }

                if (current is SocketException socketEx)
                {
                    switch (socketEx.SocketErrorCode)
                    {
                        case SocketError.OperationAborted:
                        case SocketError.Interrupted:
                        case SocketError.ConnectionAborted:
                        case SocketError.ConnectionReset:
                        case SocketError.Shutdown:
                            return true;
                    }
                }
            }

            return false;
        }
    }
}
