using ZB.MOM.WW.MxGateway.Contracts.Proto;

namespace ZB.MOM.WW.MxGateway.Client;

/// <summary>Extension methods for checking MxCommandReply success conditions.</summary>
public static class MxCommandReplyExtensions
{
    /// <summary>Validates that the reply has a successful protocol status (Ok or MxAccessFailure), throwing a gateway exception if not.</summary>
    /// <param name="reply">The command reply to check.</param>
    /// <returns>The same reply, for chaining.</returns>
    public static MxCommandReply EnsureProtocolSuccess(this MxCommandReply reply)
    {
        ArgumentNullException.ThrowIfNull(reply);

        ProtocolStatusCode code = reply.ProtocolStatus?.Code
            ?? ProtocolStatusCode.Unspecified;

        if (code is ProtocolStatusCode.Ok or ProtocolStatusCode.MxaccessFailure)
        {
            return reply;
        }

        throw CreateProtocolException(reply, code);
    }

    /// <summary>
    /// Validates that the reply indicates MXAccess success, throwing MxAccessException if not.
    /// Following COM semantics, only a negative HResult is a failure — positive success codes
    /// such as <c>S_FALSE</c> pass — and a status entry fails only when its category is not Ok.
    /// </summary>
    /// <param name="reply">The command reply to check.</param>
    /// <returns>The same reply, for chaining.</returns>
    public static MxCommandReply EnsureMxAccessSuccess(this MxCommandReply reply)
    {
        ArgumentNullException.ThrowIfNull(reply);

        bool mxAccessFailure = reply.ProtocolStatus?.Code is ProtocolStatusCode.MxaccessFailure;
        bool hResultFailure = reply.HasHresult && reply.Hresult < 0;
        bool statusFailure = reply.Statuses.Any(status => !status.IsSuccess());

        if (!mxAccessFailure && !hResultFailure && !statusFailure)
        {
            return reply;
        }

        throw new MxAccessException(CreateMxAccessMessage(reply), reply);
    }

    private static MxGatewayException CreateProtocolException(
        MxCommandReply reply,
        ProtocolStatusCode code)
    {
        string message = CreateProtocolMessage(reply);
        int? hResult = reply.HasHresult ? reply.Hresult : null;
        MxStatusProxy[] statuses = reply.Statuses.ToArray();

        return code switch
        {
            ProtocolStatusCode.SessionNotFound or ProtocolStatusCode.SessionNotReady
                => new MxGatewaySessionException(
                    message,
                    reply.SessionId,
                    reply.CorrelationId,
                    reply.ProtocolStatus,
                    hResult,
                    statuses),
            ProtocolStatusCode.WorkerUnavailable
                => new MxGatewayWorkerException(
                    message,
                    reply.SessionId,
                    reply.CorrelationId,
                    reply.ProtocolStatus,
                    hResult,
                    statuses),
            _
                => new MxGatewayCommandException(
                    message,
                    reply.SessionId,
                    reply.CorrelationId,
                    reply.ProtocolStatus,
                    hResult,
                    statuses),
        };
    }

    private static string CreateProtocolMessage(MxCommandReply reply)
    {
        string statusMessage = string.IsNullOrWhiteSpace(reply.ProtocolStatus?.Message)
            ? "Gateway protocol failure."
            : reply.ProtocolStatus.Message;

        return $"{statusMessage} code={reply.ProtocolStatus?.Code}; session={reply.SessionId}; correlation={reply.CorrelationId}";
    }

    private static string CreateMxAccessMessage(MxCommandReply reply)
    {
        string statusSummary = reply.Statuses.Count is 0
            ? "no MXSTATUS_PROXY entries"
            : string.Join("; ", reply.Statuses.Select(status => status.ToDiagnosticSummary()));

        string hResult = reply.HasHresult
            ? $"0x{reply.Hresult:X8}"
            : "none";

        return $"MXAccess command failed. kind={reply.Kind}; hresult={hResult}; statuses={statusSummary}";
    }
}
