using Org.BouncyCastle.Tls;
using Prospect.Unreal.Net.Dtls;
using Prospect.Unreal.Serialization;
using Serilog;

namespace Prospect.Unreal.Net;

/// <summary>
/// Composant de chiffrement DTLS-PSK côté serveur, pendant du DTLSHandlerComponent
/// d'UE (la pile client est [DTLSHandlerComponent, StatelessConnectHandlerComponent]).
///
/// Le client exige le chiffrement : il annonce son identité PSK (= user_id) dans
/// NMT_Hello, et on répond NMT_EncryptionAck puis on établit un DTLS-PSK. Ici on
/// implémente le côté serveur (BouncyCastle) ; la clé provient du DtlsPskStore.
///
/// ⚠️ Première implémentation — le framing exact DTLS-sur-PacketHandler d'UE et
/// l'injection des records de handshake dans la voie d'émission demandent une
/// itération EN LIVE contre le vrai client (invalidable hors client).
/// </summary>
public sealed class DTLSHandlerComponent : HandlerComponent
{
    private static readonly ILogger Logger = Log.ForContext<DTLSHandlerComponent>();

    private DtlsPacketTransport? _transport;
    private DtlsTransport? _dtls;             // non-null une fois le handshake terminé
    private Thread? _handshakeThread;
    private volatile bool _established;
    private volatile bool _failed;

    public DTLSHandlerComponent(PacketHandler handler) : base(handler, nameof(DTLSHandlerComponent))
    {
        RequiresHandshake = true;
    }

    public override void Initialize()
    {
        // Inactif tant qu'on n'a pas la PSK (via BeginHandshake) : les connexions
        // sans EncryptionToken passent en clair.
        SetActive(false);
        Initialized();
    }

    public override bool IsValid() => true;

    /// <summary>
    /// Démarre le serveur DTLS-PSK pour une identité (user_id) donnée. <paramref name="sendRecord"/>
    /// émet un record DTLS vers le client (voie bas-niveau, hors chiffrement).
    /// </summary>
    public void BeginHandshake(DtlsPskStore store, string identity, Action<byte[]> sendRecord)
    {
        if (store.Get(identity) == null)
        {
            Logger.Warning("Pas de PSK pour l'identité {Identity} — DTLS impossible (fournir PROSPECT_DTLS_PSKS)", identity);
            _failed = true;
            return;
        }

        _transport = new DtlsPacketTransport(rec =>
        {
            Logger.Information("[DTLS] Outgoing record {N}o head={H}", rec.Length,
                Convert.ToHexString(rec.AsSpan(0, Math.Min(rec.Length, 12))));
            sendRecord(rec);
        });
        SetActive(true);
        Logger.Information("[DTLS] BeginHandshake pour {Identity} — serveur DTLS-PSK démarré", identity);

        _handshakeThread = new Thread(() =>
        {
            try
            {
                var server = new ProspectPskTlsServer(store);
                _dtls = new DtlsServerProtocol().Accept(server, _transport);
                _established = true;
                Logger.Information("Handshake DTLS-PSK établi pour {Identity}", identity);
            }
            catch (Exception ex)
            {
                _failed = true;
                Logger.Error(ex, "Handshake DTLS-PSK échoué pour {Identity}", identity);
            }
        })
        {
            IsBackground = true,
            Name = "dtls-handshake",
        };
        _handshakeThread.Start();
    }

    public override void Incoming(FBitReader packet)
    {
        Logger.Information("[DTLS] Incoming CALLED active={A} transportNull={T} failed={F} established={E} bitsLeft={B}",
            IsActive(), _transport == null, _failed, _established, packet.GetBitsLeft());

        if (!IsActive() || _transport == null || _failed)
        {
            return; // passthrough (connexion non chiffrée)
        }

        var record = ReadAlignedBytes(packet);
        Logger.Information("[DTLS] Incoming nbytes={N} head={H}", record.Length,
            Convert.ToHexString(record.AsSpan(0, Math.Min(record.Length, 12))));
        if (record.Length == 0)
        {
            return;
        }

        // Alimente la pile DTLS (handshake OU données applicatives).
        _transport.Feed(record);

        if (!_established || _dtls == null)
        {
            // Record de handshake : consommé par le thread, rien à remonter au pipeline.
            packet.SetData(Array.Empty<byte>(), 0);
            return;
        }

        // Données applicatives : déchiffrer et remplacer le contenu du paquet.
        var plain = new byte[_dtls.GetReceiveLimit()];
        var n = _dtls.Receive(plain, 0, plain.Length, 0);
        if (n > 0)
        {
            packet.SetData(plain[..n], (long)n * 8);
        }
        else
        {
            packet.SetData(Array.Empty<byte>(), 0);
        }
    }

    public override void Outgoing(ref FBitWriter packet, FOutPacketTraits traits)
    {
        if (!IsActive() || !_established || _dtls == null)
        {
            return; // avant établissement : le handshake gère ses propres records
        }

        var plain = packet.GetData();
        var nbytes = (int)packet.GetNumBytes();
        _dtls.Send(plain, 0, nbytes); // le record chiffré part via le callback du transport
    }

    public override int GetReservedPacketBits()
    {
        // Marge d'en-tête DTLS (record 13o + AEAD ~40o) — valeur à affiner en live.
        return 64 * 8;
    }

    public bool IsEstablished => _established;

    public bool HasFailed => _failed;

    private static byte[] ReadAlignedBytes(FBitReader packet)
    {
        var bytes = packet.GetBytesLeft();
        if (bytes <= 0)
        {
            return Array.Empty<byte>();
        }

        var buf = new byte[bytes];
        unsafe
        {
            fixed (byte* p = buf)
            {
                packet.Serialize(p, bytes);
            }
        }
        return buf;
    }
}
