using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// Binary input that reads directly from a ReadOnlySequence (e.g. SignalR pipe, network stream).
/// Iterates segments lazily via TryGet — no upfront ArraySegment[] allocation.
///
/// The context's _buffer always points to the current segment's backing byte[] (zero-copy).
/// Cross-boundary values (straddling two+ segments) are copied into a small ArrayPool scratch buffer.
/// After the scratch read, _afterCrossBoundary restores the context to the segment's backing array.
///
/// Typical overhead for a 225KB payload with 4096-byte segments:
/// ~224.5KB zero-copy reads, ~500 bytes scratch copies (at ~55 segment boundaries).
/// </summary>
public struct SequenceBinaryInput : IBinaryInputBase
{
    /// <summary>
    /// Multi-segment input — cross-segment advance via <see cref="TryAdvanceSegment"/> is required even on
    /// nominally single-segment sequences (the wire shape isn't known statically here; multi-segment is the
    /// safe assumption). JIT-constant <c>false</c> → <c>EnsureAvailable</c> bounds-check + advance kept.
    /// <para>Note: callers that have a single-segment array-backed sequence are redirected to the byte[] overload
    /// at the entry point (see <c>AcBinaryDeserializer.Deserialize(ReadOnlySequence&lt;byte&gt;)</c>), which
    /// uses <see cref="ArrayBinaryInput"/> and thus benefits from the trusted-single-segment fast path.</para>
    /// </summary>
    public static bool IsTrustedSingleSegment => false;

    private ReadOnlySequence<byte> _sequence;
    private SequencePosition _nextPosition;

    // ArrayPool scratch for cross-boundary reads — lazy rent, reused across boundaries
    private byte[]? _scratchBuffer;
    private bool _afterCrossBoundary;

    // After cross-boundary: saved state of the last touched segment for restore
    private byte[]? _savedBuffer;
    private int _savedPosition;
    private int _savedBufferLength;

    /// <summary>
    /// Creates a SequenceBinaryInput from a ReadOnlySequence.
    /// Does NOT pre-extract segments — iterates lazily via TryGet.
    /// </summary>
    public SequenceBinaryInput(ReadOnlySequence<byte> sequence)
    {
        _sequence = sequence;
        _nextPosition = sequence.Start;
        _scratchBuffer = null;
        _afterCrossBoundary = false;
        _savedBuffer = null;
        _savedPosition = 0;
        _savedBufferLength = 0;
    }

    /// <summary>
    /// Provides the first segment's buffer.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Initialize(out byte[] buffer, out int position, out int bufferLength)
    {
        if (!_sequence.TryGet(ref _nextPosition, out var memory))
            throw new AcBinaryDeserializationException("Empty sequence — no segments to read.");

        ExtractArray(memory, out buffer, out position, out bufferLength);
    }

    /// <summary>
    /// Advances to the next segment when the current one is exhausted.
    /// Handles cross-boundary reads by copying overlapping bytes to a scratch buffer.
    /// </summary>
    [MethodImpl(MethodImplOptions.NoInlining)]
    public bool TryAdvanceSegment(ref byte[] buffer, ref int position, ref int bufferLength, int needed)
    {
        // After cross-boundary scratch read: restore to the last touched segment's backing array
        if (_afterCrossBoundary)
        {
            _afterCrossBoundary = false;
            buffer = _savedBuffer!;
            position = _savedPosition;
            bufferLength = _savedBufferLength;

            // Restored segment has enough bytes for the requested read — done
            if (bufferLength - position >= needed)
                return true;

            // Not enough bytes in restored segment — fall through to cross-boundary or next segment
        }

        var remaining = bufferLength - position;

        if (remaining > 0 && remaining < needed)
            return TryReadCrossBoundary(ref buffer, ref position, ref bufferLength, needed, remaining);

        // Current segment fully consumed — advance to next
        if (!TryLoadNextSegment(ref buffer, ref position, ref bufferLength))
            return false;

        // Loaded segment smaller than needed — cross-boundary into subsequent segments
        remaining = bufferLength - position;
        if (remaining < needed)
            return TryReadCrossBoundary(ref buffer, ref position, ref bufferLength, needed, remaining);

        return true;
    }

    /// <summary>
    /// Returns the ArrayPool scratch buffer if one was rented.
    /// Must be called after deserialization completes.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Release()
    {
        if (_scratchBuffer != null)
        {
            ArrayPool<byte>.Shared.Return(_scratchBuffer);
            _scratchBuffer = null;
        }
    }

    /// <summary>
    /// Loads the next segment from the sequence via TryGet.
    /// Extracts the backing byte[] for zero-copy access.
    /// </summary>
    private bool TryLoadNextSegment(ref byte[] buffer, ref int position, ref int bufferLength)
    {
        if (!_sequence.TryGet(ref _nextPosition, out var memory) || memory.Length == 0)
            return false;

        ExtractArray(memory, out buffer, out position, out bufferLength);
        return true;
    }

    /// <summary>
    /// Handles a read that spans N segments by copying the overlapping bytes
    /// into an ArrayPool scratch buffer. After this read, the next TryAdvanceSegment
    /// restores the context to the last touched segment's backing array.
    /// </summary>
    private bool TryReadCrossBoundary(ref byte[] buffer, ref int position, ref int bufferLength, int needed, int remaining)
    {
        // Rent scratch (or reuse if large enough)
        if (_scratchBuffer == null || _scratchBuffer.Length < needed)
        {
            if (_scratchBuffer != null)
                ArrayPool<byte>.Shared.Return(_scratchBuffer);
            _scratchBuffer = ArrayPool<byte>.Shared.Rent(needed);
        }

        // 1) Copy tail of current segment
        Buffer.BlockCopy(buffer, position, _scratchBuffer, 0, remaining);
        var filled = remaining;

        // 2) Copy from subsequent segments until we have enough
        while (filled < needed)
        {
            if (!_sequence.TryGet(ref _nextPosition, out var memory) || memory.Length == 0)
                return false;

            ExtractArray(memory, out var segArray, out var segOffset, out var segBufferLength);
            var segCount = segBufferLength - segOffset;
            var take = Math.Min(needed - filled, segCount);
            Buffer.BlockCopy(segArray, segOffset, _scratchBuffer, filled, take);
            filled += take;

            // Save last touched segment for _afterCrossBoundary restore
            _savedBuffer = segArray;
            _savedPosition = segOffset + take;
            _savedBufferLength = segBufferLength;
        }

        // Context reads from scratch buffer
        buffer = _scratchBuffer;
        position = 0;
        bufferLength = filled;
        _afterCrossBoundary = true;
        return true;
    }

    /// <summary>
    /// Extracts the backing byte[] from a ReadOnlyMemory segment.
    /// Array-backed (99.9%): zero-copy reference to backing array.
    /// Non-array-backed (native memory): copies to a managed byte[].
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static void ExtractArray(ReadOnlyMemory<byte> memory, out byte[] buffer, out int position, out int bufferLength)
    {
        if (MemoryMarshal.TryGetArray(memory, out var segment))
        {
            buffer = segment.Array!;
            position = segment.Offset;
            bufferLength = segment.Offset + segment.Count;
        }
        else
        {
            var temp = new byte[memory.Length];
            memory.Span.CopyTo(temp);
            buffer = temp;
            position = 0;
            bufferLength = temp.Length;
        }
    }
}
