using System;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Worker.Bootstrap;

namespace ZB.MOM.WW.MxGateway.Worker.Ipc;

/// <summary>Configuration options for the worker frame protocol.</summary>
public sealed class WorkerFrameProtocolOptions
{
    /// <summary>Default maximum message size in bytes (16 MB).</summary>
    public const int DefaultMaxMessageBytes = 16 * 1024 * 1024;

    /// <summary>
    /// Upper ceiling the worker will accept for a gateway-negotiated frame maximum
    /// (<c>GatewayHello.max_frame_bytes</c>). Matches the gateway's own configuration ceiling
    /// so a nonsensical negotiated value is rejected at the handshake rather than driving an absurd
    /// per-frame allocation. 256 MiB.
    /// </summary>
    public const int MaxNegotiableFrameBytes = 256 * 1024 * 1024;

    /// <summary>
    /// Lower floor the worker will accept for a gateway-negotiated frame maximum
    /// (<c>GatewayHello.max_frame_bytes</c>). Matches the gateway's own
    /// <c>GatewayOptionsValidator.MinimumMaxMessageBytes</c> validation floor so the worker never
    /// rejects a value the gateway's own validator accepts as legal configuration, yet a nonsensical
    /// tiny value (a gateway bug or a foreign/old peer) is rejected at the handshake rather than
    /// leaving a session that handshakes cleanly and then fails every subsequent frame with
    /// per-frame size errors. 1024 bytes still guarantees hellos, heartbeats, acks, and faults fit.
    /// </summary>
    public const int MinNegotiableFrameBytes = 1024;

    /// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class from WorkerOptions.</summary>
    /// <param name="options">Worker initialization options.</param>
    public WorkerFrameProtocolOptions(WorkerOptions options)
        : this(
            options?.SessionId ?? throw new ArgumentNullException(nameof(options)),
            options.ProtocolVersion,
            options.Nonce,
            DefaultMaxMessageBytes)
    {
    }

    /// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class with default max message bytes.</summary>
    /// <param name="sessionId">Identifier of the session.</param>
    /// <param name="protocolVersion">Protocol version.</param>
    /// <param name="nonce">Nonce for startup validation.</param>
    public WorkerFrameProtocolOptions(
        string sessionId,
        uint protocolVersion,
        string nonce)
        : this(
            sessionId,
            protocolVersion,
            nonce,
            DefaultMaxMessageBytes)
    {
    }

    /// <summary>Initializes a new instance of the WorkerFrameProtocolOptions class with all parameters.</summary>
    /// <param name="sessionId">Identifier of the session.</param>
    /// <param name="protocolVersion">Protocol version.</param>
    /// <param name="nonce">Nonce for startup validation.</param>
    /// <param name="maxMessageBytes">Maximum message size in bytes.</param>
    public WorkerFrameProtocolOptions(
        string sessionId,
        uint protocolVersion,
        string nonce,
        int maxMessageBytes)
    {
        if (string.IsNullOrWhiteSpace(sessionId))
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                "Worker frame protocol requires a session id.");
        }

        if (protocolVersion == 0)
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                "Worker frame protocol requires a non-zero protocol version.");
        }

        if (protocolVersion != GatewayContractInfo.WorkerProtocolVersion)
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.ProtocolVersionMismatch,
                $"Worker frame protocol version {protocolVersion} is not supported.");
        }

        if (string.IsNullOrWhiteSpace(nonce))
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                "Worker frame protocol requires a nonce.");
        }

        if (maxMessageBytes <= 0)
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                "Worker frame protocol max message size must be greater than zero.");
        }

        SessionId = sessionId;
        ProtocolVersion = protocolVersion;
        Nonce = nonce;
        MaxMessageBytes = maxMessageBytes;
    }

    /// <summary>Gets the session ID for the worker protocol.</summary>
    public string SessionId { get; }

    /// <summary>Gets the protocol version.</summary>
    public uint ProtocolVersion { get; }

    /// <summary>Gets the nonce for startup validation.</summary>
    public string Nonce { get; }

    /// <summary>
    /// Gets the maximum worker-frame message size in bytes. Initialized from the constructor and
    /// then adopted once from the gateway-negotiated value during the startup handshake
    /// (<c>GatewayHello.max_frame_bytes</c>) via <see cref="AdoptNegotiatedMaxMessageBytes"/>,
    /// before the message loop starts. Not mutated afterwards, so the single-threaded handshake write
    /// is safe for the reader/writer that share this instance.
    /// </summary>
    public int MaxMessageBytes { get; private set; }

    /// <summary>
    /// Adopts the gateway-negotiated frame maximum conveyed in <c>GatewayHello.max_frame_bytes</c>.
    /// A value of 0 (an older gateway that never set the field) is ignored and the
    /// constructor default is kept. A value outside the accepted range
    /// [<see cref="MinNegotiableFrameBytes"/>, <see cref="MaxNegotiableFrameBytes"/>] is rejected so a
    /// nonsensical negotiated value faults at the handshake rather than mid-session.
    /// </summary>
    /// <param name="negotiatedMaxFrameBytes">The gateway-negotiated maximum, or 0 for "keep default".</param>
    internal void AdoptNegotiatedMaxMessageBytes(uint negotiatedMaxFrameBytes)
    {
        if (negotiatedMaxFrameBytes == 0)
        {
            return;
        }

        if (negotiatedMaxFrameBytes < MinNegotiableFrameBytes)
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                $"GatewayHello negotiated frame maximum {negotiatedMaxFrameBytes} is below the worker floor "
                + $"of {MinNegotiableFrameBytes} bytes.");
        }

        if (negotiatedMaxFrameBytes > MaxNegotiableFrameBytes)
        {
            throw new WorkerFrameProtocolException(
                WorkerFrameProtocolErrorCode.InvalidConfiguration,
                $"GatewayHello negotiated frame maximum {negotiatedMaxFrameBytes} exceeds the worker ceiling "
                + $"of {MaxNegotiableFrameBytes} bytes.");
        }

        MaxMessageBytes = (int)negotiatedMaxFrameBytes;
    }
}
