using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Quic;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Crysome.Common.Network;
using Crysome.Common.Network.Packets;
using Crysome.Common.Network.Transport;
using Crysome.Server.Data;

namespace Crysome.Server.Network;

public class CrysomeServer
{
	public readonly PacketChannel PacketChannel;

	private QuicListener _listener;

	private CancellationTokenSource _cts;

	private Task _acceptTask;

	private X509Certificate2 _serverCert;

	private UdpClient _legacySock;

	private Task _legacyTask;

	private readonly ConcurrentDictionary<CrysomeClient, string> _clientKeys = new ConcurrentDictionary<CrysomeClient, string>();

	private readonly ConcurrentDictionary<uint, CrysomeClient> _legacySessions = new ConcurrentDictionary<uint, CrysomeClient>();

	private readonly ConcurrentDictionary<uint, string> _legacySessionKeys = new ConcurrentDictionary<uint, string>();

	private int _nextUdpToken = 1;

	private readonly ConcurrentDictionary<uint, CrysomeClient> _udpSessionMap = new ConcurrentDictionary<uint, CrysomeClient>();

	public ConcurrentDictionary<string, CrysomeClient> ConnectedClients { get; private set; }

	public bool Listening { get; private set; }

	public int Port { get; set; } = 7777;

	public int QuicPort { get; set; } = 7778;

	public AppDataStore Store { get; set; }

	public int ClientCount => ConnectedClients.Count;

	public event EventHandler<PacketEventArgs> PacketReceived;

	public event EventHandler<ClientEventArgs> ClientConnected;

	public event EventHandler<ClientEventArgs> ClientDisconnected;

	public event EventHandler<UdpFrameEventArgs> UdpFrameReceived;

	public CrysomeServer()
	{
		PacketChannel = new PacketChannel();
		ConnectedClients = new ConcurrentDictionary<string, CrysomeClient>();
	}

	public uint RegisterUdpSession(CrysomeClient client)
	{
		uint num = (uint)Interlocked.Increment(ref _nextUdpToken);
		_udpSessionMap[num] = client;
		client.UdpSessionToken = num;
		return num;
	}

	public void UnregisterUdpSession(CrysomeClient client)
	{
		if (client != null && client.UdpSessionToken != 0)
		{
			_udpSessionMap.TryRemove(client.UdpSessionToken, out var _);
		}
	}

	public void Start()
	{
		if (Listening)
		{
			Stop();
		}
		bool num = QuicPort > 0;
		bool flag = Port > 0;
		if (!num && !flag)
		{
			throw new InvalidOperationException("At least one of Port (RUDP) or QuicPort (QUIC) must be greater than 0.");
		}
		_cts = new CancellationTokenSource();
		if (num)
		{
			if (!QuicListener.IsSupported)
			{
				if (!flag)
				{
					throw new InvalidOperationException("QUIC listener is not supported. Use Windows 11 / Server 2022 or newer with MsQuic enabled, or set QuicPort to 0 and use RUDP only on Port.");
				}
			}
			else
			{
				_serverCert = QuicCertificateHelper.CreateServerCertificate();
				_listener = QuicTransportSession.StartListenerAsync(QuicPort, _serverCert, _cts.Token).GetAwaiter().GetResult();
			}
		}
		if (flag)
		{
			_legacySock = new UdpClient(Port);
			_legacySock.Client.ReceiveBufferSize = 8388608;
			_legacySock.Client.SendBufferSize = 8388608;
			_legacyTask = Task.Run(() => LegacyReceiveLoop(_cts.Token));
		}
		Listening = true;
		if (_listener != null)
		{
			_acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token));
		}
	}

	public void Stop()
	{
		Listening = false;
		try
		{
			_cts?.Cancel();
		}
		catch
		{
		}
		try
		{
			_listener?.DisposeAsync().AsTask().Wait(3000);
		}
		catch
		{
		}
		_listener = null;
		try
		{
			_legacySock?.Close();
		}
		catch
		{
		}
		_legacySock = null;
		foreach (KeyValuePair<string, CrysomeClient> connectedClient in ConnectedClients)
		{
			try
			{
				connectedClient.Value.Disconnect();
			}
			catch
			{
			}
		}
		ConnectedClients.Clear();
		_clientKeys.Clear();
		_legacySessions.Clear();
		_legacySessionKeys.Clear();
		_udpSessionMap.Clear();
	}

	private async Task LegacyReceiveLoop(CancellationToken ct)
	{
		uint sessionId = default(uint);
		byte b = default(byte);
		uint num = default(uint);
		uint num2 = default(uint);
		ushort num3 = default(ushort);
		ushort num4 = default(ushort);
		byte b2 = default(byte);
		while (!ct.IsCancellationRequested && Listening && _legacySock != null)
		{
			try
			{
				UdpReceiveResult udpReceiveResult = await _legacySock.ReceiveAsync().ConfigureAwait(continueOnCapturedContext: false);
				byte[] buffer = udpReceiveResult.Buffer;
				if (!RudpChannel.ParseHeader(buffer, buffer.Length, out sessionId, out b, out num, out num2, out num3, out num4, out b2))
				{
					continue;
				}
				if (_legacySessions.TryGetValue(sessionId, out var client))
				{
					goto IL_0275;
				}
				if ((b & 9) == 0 || sessionId == 0)
				{
					continue;
				}
				RudpChannel val = new RudpChannel(sessionId, udpReceiveResult.RemoteEndPoint, _legacySock);
				val.UnreliableFrameReceived += delegate(byte typeId, byte[] data)
				{
					if (_legacySessions.TryGetValue(sessionId, out var value))
					{
						try
						{
							this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(value, typeId, data));
						}
						catch
						{
						}
					}
				};
				val.Disconnected += delegate
				{
					LegacyClientGone(sessionId);
				};
				val.Start();
				client = new CrysomeClient(val);
				string key = Guid.NewGuid().ToString("N");
				_legacySessions[sessionId] = client;
				_legacySessionKeys[sessionId] = key;
				ConnectedClients[key] = client;
				Task.Run(delegate
				{
					ClientReadLoop(key, client, ct);
				}, ct);
				OnClientConnected(client);
				goto IL_0275;
				IL_0275:
				CrysomeClient obj = client;
				if (obj != null)
				{
					RudpChannel rudpChannel = obj.GetRudpChannel();
					if (rudpChannel != null)
					{
						rudpChannel.FeedDatagram(buffer);
					}
				}
			}
			catch (ObjectDisposedException)
			{
				break;
			}
			catch
			{
			}
		}
	}

	private void LegacyClientGone(uint sessionId)
	{
		if (_legacySessions.TryRemove(sessionId, out var value))
		{
			if (_legacySessionKeys.TryRemove(sessionId, out var value2))
			{
				ConnectedClients.TryRemove(value2, out var _);
			}
			UnregisterUdpSession(value);
			OnClientDisconnected(value);
		}
	}

	private async Task AcceptLoopAsync(CancellationToken ct)
	{
		while (!ct.IsCancellationRequested && Listening && _listener != null)
		{
			try
			{
				QuicConnection conn = await _listener.AcceptConnectionAsync(ct).ConfigureAwait(continueOnCapturedContext: false);
				Task.Run(() => HandleConnectionAsync(conn, ct), ct);
			}
			catch (ObjectDisposedException)
			{
				break;
			}
			catch
			{
			}
		}
	}

	private async Task HandleConnectionAsync(QuicConnection conn, CancellationToken ct)
	{
		QuicTransportSession session = null;
		CrysomeClient client = null;
		string key = null;
		try
		{
			session = await QuicTransportSession.AcceptServerAsync(conn, ct).ConfigureAwait(continueOnCapturedContext: false);
			client = new CrysomeClient((ITransportSession)(object)session, true);
			key = Guid.NewGuid().ToString("N");
			_clientKeys[client] = key;
			ConnectedClients[key] = client;
			session.UnreliableFrameReceived += delegate(byte typeId, byte[] data)
			{
				try
				{
					this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(client, typeId, data));
				}
				catch
				{
				}
			};
			Task.Run(delegate
			{
				ClientReadLoop(key, client, ct);
			}, ct);
			OnClientConnected(client);
		}
		catch
		{
			try
			{
				QuicTransportSession obj2 = session;
				if (obj2 != null)
				{
					obj2.Dispose();
				}
			}
			catch
			{
			}
			if (client != null && key != null)
			{
				ClientGone(client, key);
			}
		}
	}

	private void ClientGone(CrysomeClient client, string key)
	{
		if (client == null)
		{
			return;
		}
		CrysomeClient value;
		string value2;
		foreach (KeyValuePair<uint, CrysomeClient> legacySession in _legacySessions)
		{
			if (legacySession.Value == client)
			{
				_legacySessions.TryRemove(legacySession.Key, out value);
				_legacySessionKeys.TryRemove(legacySession.Key, out value2);
				break;
			}
		}
		_clientKeys.TryRemove(client, out value2);
		if (key != null)
		{
			ConnectedClients.TryRemove(key, out value);
		}
		UnregisterUdpSession(client);
		OnClientDisconnected(client);
	}

	private void ClientReadLoop(string key, CrysomeClient client, CancellationToken ct)
	{
		try
		{
			while (!ct.IsCancellationRequested && client.IsConnected)
			{
				IPacket packet = client.ReadPacket();
				OnPacketReceived(client, packet);
			}
		}
		catch (IOException)
		{
		}
		catch
		{
		}
		finally
		{
			ClientGone(client, key);
		}
	}

	public void DisconnectClient(CrysomeClient client)
	{
		if (client == null)
		{
			return;
		}
		try
		{
			client.Disconnect();
		}
		catch
		{
		}
	}

	public List<CrysomeClient> GetClientSnapshot()
	{
		return new List<CrysomeClient>(ConnectedClients.Values);
	}

	private void OnClientConnected(CrysomeClient client)
	{
		this.ClientConnected?.Invoke(this, new ClientEventArgs(client));
		if (Store == null || !Store.Settings.TelegramEnabled || string.IsNullOrEmpty(Store.Settings.TelegramToken) || string.IsNullOrEmpty(Store.Settings.TelegramChatId))
		{
			return;
		}
		string ip = client.RemoteAddress?.Address?.ToString() ?? "Unknown";
		Task.Run(async delegate
		{
			try
			{
				string stringToEscape = "\ud83d\udd14 *New Client Connected*\nIP: " + ip;
				string requestUri = $"https://api.telegram.org/bot{Store.Settings.TelegramToken}/sendMessage?chat_id={Store.Settings.TelegramChatId}&text={Uri.EscapeDataString(stringToEscape)}&parse_mode=Markdown";
				using HttpClient hc = new HttpClient();
				await hc.GetAsync(requestUri);
			}
			catch
			{
			}
		});
	}

	private void OnClientDisconnected(CrysomeClient client)
	{
		this.ClientDisconnected?.Invoke(this, new ClientEventArgs(client));
	}

	private void OnPacketReceived(CrysomeClient client, IPacket packet)
	{
		this.PacketReceived?.Invoke(this, new PacketEventArgs(client, packet));
		PacketChannel.HandlePacket(client, packet);
	}
}
