using System.Collections.Concurrent;
using Org.BouncyCastle.Tls;

namespace Prospect.Unreal.Net.Dtls;

/// <summary>
/// Adapte l'API bloquante DTLS de BouncyCastle (DatagramTransport) au modèle
/// « un paquet à la fois » du PacketHandler d'Unreal.
///
/// - <see cref="Feed"/> : on y pousse les records DTLS reçus (depuis Incoming) ;
///   BouncyCastle les consomme via <see cref="Receive"/>.
/// - <see cref="Send"/> : BouncyCastle y écrit les records à émettre ; on les
///   récupère via le callback <c>onSend</c> pour les renvoyer dans le pipeline.
///
/// Le handshake tourne sur un thread dédié (Accept est bloquant) ; les records
/// applicatifs, eux, sont traités de façon synchrone dans Incoming/Outgoing.
/// </summary>
public sealed class DtlsPacketTransport : DatagramTransport
{
    private const int Mtu = 1500;

    private readonly BlockingCollection<byte[]> _incoming = new(new ConcurrentQueue<byte[]>());
    private readonly Action<byte[]> _onSend;

    public DtlsPacketTransport(Action<byte[]> onSend)
    {
        _onSend = onSend;
    }

    /// <summary>Pousse un record DTLS reçu du réseau vers la pile BouncyCastle.</summary>
    public void Feed(byte[] record)
    {
        if (!_incoming.IsAddingCompleted)
        {
            _incoming.Add(record);
        }
    }

    public int Receive(byte[] buf, int off, int len, int waitMillis)
    {
        try
        {
            if (!_incoming.TryTake(out var record, waitMillis))
            {
                return -1; // timeout : BouncyCastle re-tentera / retransmettra
            }

            var n = Math.Min(len, record.Length);
            Array.Copy(record, 0, buf, off, n);
            return n;
        }
        catch (Exception)
        {
            return -1;
        }
    }

    public int Receive(Span<byte> buffer, int waitMillis)
    {
        try
        {
            if (!_incoming.TryTake(out var record, waitMillis))
            {
                return -1;
            }

            var n = Math.Min(buffer.Length, record.Length);
            record.AsSpan(0, n).CopyTo(buffer);
            return n;
        }
        catch (Exception)
        {
            return -1;
        }
    }

    public void Send(byte[] buf, int off, int len)
    {
        var record = new byte[len];
        Array.Copy(buf, off, record, 0, len);
        _onSend(record);
    }

    public void Send(ReadOnlySpan<byte> buffer)
    {
        _onSend(buffer.ToArray());
    }

    public int GetReceiveLimit() => Mtu;

    public int GetSendLimit() => Mtu;

    public void Close()
    {
        _incoming.CompleteAdding();
    }
}
