using System.Buffers.Binary;
using System.Text;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Recorder;

/// <summary>
///     Compact, allocation-light little-endian binary (de)serializer for
///     <see cref="HistorizationOutboxEntry"/> records persisted to the FasterLog outbox. The entry is
///     all primitives, so a fixed binary layout is smaller and faster than JSON and avoids any
///     reflection at the durable boundary.
/// </summary>
/// <remarks>
///     Layout (little-endian): <c>Guid(16) | tagByteLen:int32(4) | tagUtf8(n) | value:double(8) |
///     quality:uint16(2) | timestamp:int64(8)</c>. The timestamp is <see cref="DateTime.ToBinary"/>,
///     which round-trips <see cref="DateTimeKind"/>.
/// </remarks>
internal static class HistorizationOutboxEntrySerializer
{
    /// <summary>Serializes <paramref name="entry"/> to a fixed-layout little-endian byte array.</summary>
    /// <param name="entry">The outbox entry to serialize.</param>
    /// <returns>The fixed-layout little-endian byte array.</returns>
    public static byte[] Serialize(HistorizationOutboxEntry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);

        int tagLen = Encoding.UTF8.GetByteCount(entry.Tag);
        var buffer = new byte[16 + 4 + tagLen + 8 + 2 + 8];
        Span<byte> span = buffer;

        entry.Id.TryWriteBytes(span[..16]);
        BinaryPrimitives.WriteInt32LittleEndian(span.Slice(16, 4), tagLen);
        Encoding.UTF8.GetBytes(entry.Tag, span.Slice(20, tagLen));

        int p = 20 + tagLen;
        BinaryPrimitives.WriteDoubleLittleEndian(span.Slice(p, 8), entry.NumericValue);
        p += 8;
        BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(p, 2), entry.Quality);
        p += 2;
        BinaryPrimitives.WriteInt64LittleEndian(span.Slice(p, 8), entry.TimestampUtc.ToBinary());

        return buffer;
    }

    /// <summary>Reconstructs a <see cref="HistorizationOutboxEntry"/> from its serialized bytes.</summary>
    /// <param name="span">The fixed-layout little-endian bytes produced by <see cref="Serialize"/>.</param>
    /// <returns>The reconstructed outbox entry.</returns>
    public static HistorizationOutboxEntry Deserialize(ReadOnlySpan<byte> span)
    {
        var id = new Guid(span[..16]);
        int tagLen = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(16, 4));
        string tag = Encoding.UTF8.GetString(span.Slice(20, tagLen));

        int p = 20 + tagLen;
        double value = BinaryPrimitives.ReadDoubleLittleEndian(span.Slice(p, 8));
        p += 8;
        ushort quality = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(p, 2));
        p += 2;
        long timestamp = BinaryPrimitives.ReadInt64LittleEndian(span.Slice(p, 8));

        return new HistorizationOutboxEntry(id, tag, value, quality, DateTime.FromBinary(timestamp));
    }
}
