using System.Net;

namespace DeepDrftManager.Services;

/// <summary>
/// An <see cref="HttpContent"/> that streams a source stream to the wire while reporting cumulative
/// bytes written after each chunk. This is the single source of truth for both the upload progress
/// meter and the idle/heartbeat timeout: every reported tick advances the UI <em>and</em> resets the
/// idle deadline, so one mechanism feeds both concerns.
/// </summary>
/// <remarks>
/// Wrap the audio payload (not the whole multipart container) so <see cref="TryComputeLength"/>
/// returns the file length and the reported byte counts map directly onto "bytes of this file".
/// </remarks>
public sealed class ProgressStreamContent : HttpContent
{
    // 80 KB: large enough to keep the socket fed on a healthy link, small enough that a stalled
    // connection trips the idle window without a multi-MB write swallowing the whole heartbeat budget.
    private const int CopyBufferSize = 81_920;

    private readonly Stream _source;
    private readonly long _length;
    private readonly Action<long> _onBytesWritten;

    /// <param name="source">The payload stream. Read once, sequentially — not seekable-rewound.</param>
    /// <param name="length">Total bytes the source will yield; sets Content-Length and the meter denominator.</param>
    /// <param name="onBytesWritten">Invoked after each chunk with the cumulative bytes written so far.</param>
    public ProgressStreamContent(Stream source, long length, Action<long> onBytesWritten)
    {
        _source = source;
        _length = length;
        _onBytesWritten = onBytesWritten;
    }

    // Token-aware overload (.NET 5+): HttpClient calls this on the send path and passes the request's
    // CancellationToken, so the idle-heartbeat CTS aborts an in-flight read/write promptly — not just
    // between chunks. The parameterless base override delegates here with CancellationToken.None.
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
        => CopyAsync(stream, cancellationToken);

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
        => CopyAsync(stream, CancellationToken.None);

    private async Task CopyAsync(Stream stream, CancellationToken cancellationToken)
    {
        var buffer = new byte[CopyBufferSize];
        long written = 0;
        int read;
        while ((read = await _source.ReadAsync(buffer, cancellationToken)) > 0)
        {
            await stream.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
            written += read;
            // Report after the bytes are on the wire — a tick means real forward progress, which is
            // exactly the signal the idle heartbeat must reset on.
            _onBytesWritten(written);
        }
    }

    protected override bool TryComputeLength(out long length)
    {
        length = _length;
        return true;
    }
}
