using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

namespace Crysome.Common.Network.FileTransfer;

public sealed class FtSender
{
	public const int ChunkSize = 1200;

	private const int AbortTimeoutMs = 600000;

	private const int SackWaitMs = 500;

	private const int ChunkPaceMs = 2;

	private readonly CrysomeClient _client;

	private readonly byte[] _data;

	private readonly string _fileName;

	private readonly byte[] _sha256;

	private volatile bool _done;

	private volatile int _ackedChunks;

	public Action<uint, bool, string> OnDone;

	private static int _nextId;

	public uint TransferId { get; }

	public int TotalChunks => Math.Max(1, (_data.Length + 1200 - 1) / 1200);

	public FtSender(CrysomeClient client, string fileName, byte[] data)
	{
		_client = client;
		_data = data;
		_fileName = fileName;
		TransferId = (uint)Interlocked.Increment(ref _nextId);
		using SHA256 sHA = SHA256.Create();
		_sha256 = sHA.ComputeHash(data);
	}

	public void Start()
	{
		Task.Run((Action)Run);
	}

	public void HandleDone(FtDonePacket p)
	{
		if (p.TransferId == TransferId)
		{
			_done = true;
			OnDone?.Invoke(TransferId, p.Success, p.Message);
		}
	}

	public void HandleSack(FtSackPacket p)
	{
		if (p.TransferId != TransferId || _done)
		{
			return;
		}
		if (p.HighestContiguous >= 0)
		{
			_ackedChunks = Math.Max(_ackedChunks, p.HighestContiguous + 1);
		}
		if (p.NackList == null)
		{
			return;
		}
		int[] nackList = p.NackList;
		foreach (int num in nackList)
		{
			if (num >= 0 && num < TotalChunks)
			{
				SendChunk(num);
			}
		}
	}

	private void Run()
	{
		try
		{
			int totalChunks = TotalChunks;
			_client.SendPacket(new FtStartPacket
			{
				TransferId = TransferId,
				FileName = _fileName,
				TotalChunks = totalChunks,
				ChunkSize = 1200,
				TotalBytes = _data.Length,
				Sha256 = _sha256
			});
			Thread.Sleep(50);
			DateTime utcNow = DateTime.UtcNow;
			int num = 0;
			DateTime utcNow2 = DateTime.UtcNow;
			for (int i = 0; i < totalChunks; i++)
			{
				if (_done)
				{
					break;
				}
				SendChunk(i);
				if (i % 32 == 31)
				{
					Thread.Sleep(2);
				}
				if ((DateTime.UtcNow - utcNow).TotalMilliseconds > 600000.0)
				{
					_client.SendPacket(new FtAbortPacket
					{
						TransferId = TransferId,
						Reason = "Sender timeout"
					});
					OnDone?.Invoke(TransferId, arg2: false, "ERR: Send timeout");
					return;
				}
			}
			utcNow2 = DateTime.UtcNow;
			num = _ackedChunks;
			while (!_done)
			{
				Thread.Sleep(500);
				int ackedChunks = _ackedChunks;
				if (ackedChunks > num)
				{
					num = ackedChunks;
					utcNow2 = DateTime.UtcNow;
				}
				double totalMilliseconds = (DateTime.UtcNow - utcNow2).TotalMilliseconds;
				double totalMilliseconds2 = (DateTime.UtcNow - utcNow).TotalMilliseconds;
				if (totalMilliseconds > 60000.0 || totalMilliseconds2 > 600000.0)
				{
					_client.SendPacket(new FtAbortPacket
					{
						TransferId = TransferId,
						Reason = "Done-ack timeout"
					});
					OnDone?.Invoke(TransferId, arg2: false, "ERR: No confirmation from client");
					break;
				}
			}
		}
		catch (Exception ex)
		{
			_done = true;
			OnDone?.Invoke(TransferId, arg2: false, "ERR: " + ex.Message);
		}
	}

	private void SendChunk(int idx)
	{
		int num = idx * 1200;
		int num2 = Math.Min(1200, _data.Length - num);
		byte[] array = new byte[num2];
		Buffer.BlockCopy(_data, num, array, 0, num2);
		_client.SendPacket(new FtChunkPacket
		{
			TransferId = TransferId,
			ChunkIdx = idx,
			Crc32 = Crc32.Compute(_data, num, num2),
			Data = array
		});
	}
}
