using System.IO.Hashing;

namespace NATS.Server.Raft;

/// <summary>
/// Splits a snapshot byte array into fixed-size chunks for streaming transfer
/// and computes a CRC32 checksum over the entire data for integrity validation.
///
/// During an InstallSnapshot RPC the leader streams a large snapshot to a lagging
/// follower chunk-by-chunk rather than sending it in a single message. The follower
/// accumulates the chunks, reassembles them, then validates the CRC32 before
/// applying the snapshot — preventing silent data corruption in transit.
///
/// Go reference: raft.go:3500-3700 (installSnapshot chunked transfer and CRC validation)
/// </summary>
public sealed class SnapshotChunkEnumerator : IEnumerable<byte[]>
{
    private readonly byte[] _data;
    private readonly int _chunkSize;
    private uint? _crc32Value;

    /// <summary>
    /// Default chunk size matching Go's snapshot chunk transfer size (64 KiB).
    /// Go reference: raft.go snapshotChunkSize constant.
    /// </summary>
    public const int DefaultChunkSize = 65536;

    /// <summary>
    /// Creates a new chunk enumerator over <paramref name="data"/>.
    /// </summary>
    /// <param name="data">The full snapshot data to split into chunks.</param>
    /// <param name="chunkSize">Maximum size of each chunk in bytes. Defaults to 64 KiB.</param>
    /// <exception cref="ArgumentOutOfRangeException">
    /// Thrown when <paramref name="chunkSize"/> is less than or equal to zero.
    /// </exception>
    public SnapshotChunkEnumerator(byte[] data, int chunkSize = DefaultChunkSize)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(chunkSize);
        _data = data;
        _chunkSize = chunkSize;
    }

    /// <summary>
    /// The CRC32 (IEEE 802.3) checksum of the entire snapshot data.
    /// Computed lazily on first access and cached for subsequent reads.
    /// The receiver uses this value to validate the reassembled snapshot after
    /// all chunks have been received.
    /// Go reference: raft.go installSnapshot CRC validation.
    /// </summary>
    public uint Crc32Value
    {
        get
        {
            if (!_crc32Value.HasValue)
            {
                var crc = new Crc32();
                crc.Append(_data);
                _crc32Value = crc.GetCurrentHashAsUInt32();
            }
            return _crc32Value.Value;
        }
    }

    /// <summary>
    /// The total number of chunks this data splits into given the configured chunk size.
    /// </summary>
    public int ChunkCount => _data.Length == 0 ? 1 : ((_data.Length + _chunkSize - 1) / _chunkSize);

    /// <inheritdoc />
    public IEnumerator<byte[]> GetEnumerator()
    {
        if (_data.Length == 0)
        {
            yield return [];
            yield break;
        }

        var offset = 0;
        while (offset < _data.Length)
        {
            var length = Math.Min(_chunkSize, _data.Length - offset);
            var chunk = new byte[length];
            _data.AsSpan(offset, length).CopyTo(chunk);
            offset += length;
            yield return chunk;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}
