using AyCode.Core.Serializers.Binaries;
using Microsoft.Extensions.Logging;

namespace AyCode.Services.SignalRs;

/// <summary>
/// Configuration for <see cref="AcBinaryHubProtocol"/> and derived protocols.
/// Use via <c>services.Configure&lt;AcBinaryHubProtocolOptions&gt;(...)</c> in Program.cs,
/// or pass an instance directly to the protocol constructor.
/// </summary>
public sealed class AcBinaryHubProtocolOptions
{
    // --- Serializer (standalone sub-group, also usable without SignalR via ToBinary/BinaryTo) ---

    /// <summary>Binary serializer options. Default: <see cref="AcBinarySerializerOptions.Default"/>.</summary>
    public AcBinarySerializerOptions SerializerOptions { get; set; } = AcBinarySerializerOptions.Default;

    // --- Transport ---

    /// <summary>Wire format and pipeline strategy. Default: <see cref="BinaryProtocolMode.Bytes"/>.</summary>
    public BinaryProtocolMode ProtocolMode { get; set; } = BinaryProtocolMode.Bytes;

    /// <summary>Chunk size for BufferWriterBinaryOutput / AsyncPipeWriterOutput.
    /// Default: 4096 (aligns with Kestrel's slab size for optimal latency-to-first-byte).</summary>
    public int BufferSize { get; set; } = 4096;

    /// <summary>
    /// Per-chunk flush synchronization in <see cref="BinaryProtocolMode.AsyncSegment"/> mode.
    /// <list type="bullet">
    ///   <item><see cref="FlushPolicy.PerChunk"/>: commit → flush → await each chunk before
    ///   acquiring the next. Strictly bounded peak memory (~chunk × 1). No producer/flush
    ///   parallelism. Best for memory-sensitive servers and unpredictable payload sizes.</item>
    ///   <item><see cref="FlushPolicy.DoubleBuffered"/>: fire-and-forget previous flush; next chunk
    ///   waits only if previous flush hasn't completed. Peak memory ~chunk × 2 with maximum
    ///   producer/flush parallelism — the recommended balanced choice for typical streaming.</item>
    ///   <item><see cref="FlushPolicy.Coalesced"/> (default for SignalR): no per-chunk wait; the
    ///   underlying Pipe coalesces flushes adaptively up to its PauseWriterThreshold (~64 KB).
    ///   Highest throughput on bounded payloads — fast consumer never triggers a wait, slow
    ///   consumer naturally throttles through buffer pressure.</item>
    /// </list>
    /// Ignored for Bytes and Segment modes.
    /// </summary>
    public FlushPolicy FlushPolicy { get; set; } = FlushPolicy.DoubleBuffered;

    /// <summary>
    /// Maximum wait for a single synchronous <c>FlushAsync</c> before throwing
    /// <see cref="TimeoutException"/>. Protects against slow/stuck/disconnected consumers
    /// blocking the server thread indefinitely.
    /// <para>
    /// Default: 10 seconds. Rationale: AsyncSegment chunks are max 65 KB (UINT16 size field);
    /// even GPRS-class connections (~60 Kbit/s) transfer 65 KB in ~9 s. If a flush exceeds 10 s,
    /// the consumer is effectively stuck — faster failure detection is preferable to indefinite blocking.
    /// </para>
    /// <para>
    /// Note: complementary to SignalR's connection-level timeouts (<c>ClientTimeoutInterval</c>,
    /// <c>KeepAliveInterval</c>). This is a per-operation guard; SignalR timeouts manage the overall
    /// connection lifetime. Set <c>FlushTimeout &lt; ClientTimeoutInterval</c> so this guard fires first.
    /// </para>
    /// Set to <see cref="System.Threading.Timeout.InfiniteTimeSpan"/> to disable.
    /// </summary>
    public TimeSpan FlushTimeout { get; set; } = TimeSpan.FromSeconds(10);

    // --- Identity ---

    /// <summary>Protocol name in the SignalR handshake. Client and server must match. Default: "acbinary".</summary>
    public string Name { get; set; } = "acbinary";

    // --- Diagnostics ---

    /// <summary>Optional logger. When null, no protocol-internal logging occurs.</summary>
    public ILogger? Logger { get; set; }

    // --- Validation ---

    /// <summary>
    /// Validates option values and platform compatibility.
    /// Throws <see cref="ArgumentOutOfRangeException"/> / <see cref="ArgumentException"/> on invalid values,
    /// or <see cref="PlatformNotSupportedException"/> for unsupported platform/mode combinations.
    /// </summary>
    public void Validate()
    {
        // WASM + AsyncSegment send-path guard — the AsyncPipeWriterOutput sync-over-async flush
        // would block the browser's single UI thread. The receive side converts chunked wire to
        // synchronous deser automatically, so WASM clients can still *receive* AsyncSegment data
        // from a non-WASM server — they just cannot *send* via AsyncSegment themselves.
        if (OperatingSystem.IsBrowser() && ProtocolMode == BinaryProtocolMode.AsyncSegment)
            throw new PlatformNotSupportedException(
                "BinaryProtocolMode.AsyncSegment is not supported on WebAssembly. " +
                "Use BinaryProtocolMode.Bytes or BinaryProtocolMode.Segment instead.");

        if (BufferSize < 256 || BufferSize > AsyncPipeWriterOutput.MaxChunkSize)
            throw new ArgumentOutOfRangeException(nameof(BufferSize), BufferSize,
                $"BufferSize must be between 256 and {AsyncPipeWriterOutput.MaxChunkSize} (UINT16 max).");

        if (FlushTimeout <= TimeSpan.Zero && FlushTimeout != System.Threading.Timeout.InfiniteTimeSpan)
            throw new ArgumentOutOfRangeException(nameof(FlushTimeout), FlushTimeout,
                "FlushTimeout must be positive, or Timeout.InfiniteTimeSpan to disable.");

        if (string.IsNullOrWhiteSpace(Name))
            throw new ArgumentException("Name cannot be null or whitespace.", nameof(Name));
    }

    /// <summary>
    /// Creates a shallow copy. Required for DI <c>IOptions&lt;T&gt;</c> integration —
    /// the singleton from DI must not be mutated by per-connection <c>configure</c> callbacks.
    /// </summary>
    public AcBinaryHubProtocolOptions Clone() => new()
    {
        SerializerOptions = SerializerOptions,
        ProtocolMode = ProtocolMode,
        BufferSize = BufferSize,
        FlushPolicy = FlushPolicy,
        FlushTimeout = FlushTimeout,
        Name = Name,
        Logger = Logger
    };
}
