using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;

namespace ZB.MOM.WW.MxGateway.Tests.Contracts;

public sealed class ProtobufContractRoundTripTests
{
    /// <summary>Verifies that gateway descriptor contains expected public service methods.</summary>
    [Fact]
    public void GatewayDescriptor_ContainsInitialPublicServiceMethods()
    {
        var service = Assert.Single(
            MxaccessGatewayReflection.Descriptor.Services,
            descriptor => descriptor.Name == "MxAccessGateway");

        Assert.Contains(service.Methods, method => method.Name == "OpenSession");
        Assert.Contains(service.Methods, method => method.Name == "CloseSession");
        Assert.Contains(service.Methods, method => method.Name == "Invoke");
        Assert.Contains(service.Methods, method => method.Name == "StreamEvents");
        Assert.Contains(service.Methods, method => method.Name == "AcknowledgeAlarm");
        Assert.Contains(service.Methods, method => method.Name == "StreamAlarms");
    }

    /// <summary>Verifies that worker envelope descriptor contains required correlation fields.</summary>
    [Fact]
    public void WorkerEnvelopeDescriptor_ContainsRequiredCorrelationFields()
    {
        var fields = WorkerEnvelope.Descriptor.Fields.InDeclarationOrder();

        Assert.Contains(fields, field => field.Name == "protocol_version");
        Assert.Contains(fields, field => field.Name == "session_id");
        Assert.Contains(fields, field => field.Name == "sequence");
        Assert.Contains(fields, field => field.Name == "correlation_id");
    }

    /// <summary>Verifies that command request round-trips through serialization.</summary>
    [Fact]
    public void CommandRequest_RoundTripsMethodSpecificPayload()
    {
        var original = new MxCommandRequest
        {
            SessionId = "session-1",
            ClientCorrelationId = "client-correlation-1",
            Command = new MxCommand
            {
                Kind = MxCommandKind.Register,
                Register = new RegisterCommand
                {
                    ClientName = "mxaccessgw-test-client",
                },
            },
        };

        var parsed = MxCommandRequest.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.Register, parsed.Command.PayloadCase);
    }

    /// <summary>Verifies that command reply round-trips with return values and statuses.</summary>
    [Fact]
    public void CommandReply_RoundTripsHResultReturnValueOutParamsAndStatuses()
    {
        var original = new MxCommandReply
        {
            SessionId = "session-1",
            CorrelationId = "gateway-correlation-1",
            Kind = MxCommandKind.AddItem,
            ProtocolStatus = new ProtocolStatus
            {
                Code = ProtocolStatusCode.Ok,
            },
            Hresult = 0,
            ReturnValue = new MxValue
            {
                DataType = MxDataType.Integer,
                Int32Value = 1234,
                VariantType = "VT_I4",
            },
            AddItem = new AddItemReply
            {
                ItemHandle = 1234,
            },
        };
        original.Statuses.Add(new MxStatusProxy
        {
            Success = 1,
            Category = MxStatusCategory.Ok,
            DetectedBy = MxStatusSource.RespondingLmx,
            Detail = 0,
        });

        var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.True(parsed.HasHresult);
        Assert.Equal(MxCommandReply.PayloadOneofCase.AddItem, parsed.PayloadCase);
        Assert.Single(parsed.Statuses);
    }

    /// <summary>Verifies that event round-trips with value, status, and sequence.</summary>
    [Fact]
    public void Event_RoundTripsValueStatusSequenceAndBufferedBody()
    {
        var timestamp = Timestamp.FromDateTime(new DateTime(2026, 4, 26, 20, 0, 0, DateTimeKind.Utc));
        var original = new MxEvent
        {
            Family = MxEventFamily.OnBufferedDataChange,
            SessionId = "session-1",
            ServerHandle = 10,
            ItemHandle = 20,
            Value = new MxValue
            {
                DataType = MxDataType.Float,
                ArrayValue = new MxArray
                {
                    ElementDataType = MxDataType.Float,
                    FloatValues = new FloatArray
                    {
                        Values = { 1.5f, 2.5f },
                    },
                    Dimensions = { 2 },
                    VariantType = "VT_ARRAY|VT_R4",
                },
            },
            Quality = 192,
            SourceTimestamp = timestamp,
            WorkerSequence = 42,
            WorkerTimestamp = timestamp,
            GatewayReceiveTimestamp = timestamp,
            OnBufferedDataChange = new OnBufferedDataChangeEvent
            {
                DataType = MxDataType.Float,
                QualityValues = new MxArray
                {
                    ElementDataType = MxDataType.Integer,
                    Int32Values = new Int32Array
                    {
                        Values = { 192, 192 },
                    },
                    Dimensions = { 2 },
                },
                TimestampValues = new MxArray
                {
                    ElementDataType = MxDataType.Time,
                    TimestampValues = new TimestampArray
                    {
                        Values = { timestamp, timestamp },
                    },
                    Dimensions = { 2 },
                },
            },
        };
        original.Statuses.Add(new MxStatusProxy
        {
            Success = 1,
            Category = MxStatusCategory.Ok,
            DetectedBy = MxStatusSource.RespondingNmx,
            Detail = 0,
        });

        var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxEvent.BodyOneofCase.OnBufferedDataChange, parsed.BodyCase);
        Assert.Single(parsed.Statuses);
    }

    /// <summary>Verifies that worker envelope round-trips through serialization preserving protocol and command fields.</summary>
    [Fact]
    public void WorkerEnvelope_RoundTripsProtocolFieldsAndCommandBody()
    {
        var original = new WorkerEnvelope
        {
            ProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
            SessionId = "session-1",
            Sequence = 7,
            CorrelationId = "gateway-correlation-1",
            WorkerCommand = new WorkerCommand
            {
                EnqueueTimestamp = Timestamp.FromDateTime(
                    new DateTime(2026, 4, 26, 20, 5, 0, DateTimeKind.Utc)),
                Command = new MxCommand
                {
                    Kind = MxCommandKind.Advise,
                    Advise = new AdviseCommand
                    {
                        ServerHandle = 10,
                        ItemHandle = 20,
                    },
                },
            },
        };

        var parsed = WorkerEnvelope.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerCommand, parsed.BodyCase);
        Assert.Equal(MxCommand.PayloadOneofCase.Advise, parsed.WorkerCommand.Command.PayloadCase);
    }

    /// <summary>Verifies that an OnAlarmTransition event round-trips with full payload.</summary>
    [Fact]
    public void Event_RoundTripsOnAlarmTransitionWithFullPayload()
    {
        var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
        var ack = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 30, DateTimeKind.Utc));
        var original = new MxEvent
        {
            Family = MxEventFamily.OnAlarmTransition,
            SessionId = "session-1",
            WorkerSequence = 99,
            WorkerTimestamp = ack,
            GatewayReceiveTimestamp = ack,
            OnAlarmTransition = new OnAlarmTransitionEvent
            {
                AlarmFullReference = "Tank01.Level.HiHi",
                SourceObjectReference = "Tank01",
                AlarmTypeName = "AnalogLimitAlarm.HiHi",
                TransitionKind = AlarmTransitionKind.Acknowledge,
                Severity = 750,
                OriginalRaiseTimestamp = raise,
                TransitionTimestamp = ack,
                OperatorUser = "operator1",
                OperatorComment = "investigating",
                Category = "Process",
                Description = "Tank 01 high-high level",
                CurrentValue = new MxValue
                {
                    DataType = MxDataType.Float,
                    FloatValue = 95.4f,
                    VariantType = "VT_R4",
                },
                LimitValue = new MxValue
                {
                    DataType = MxDataType.Float,
                    FloatValue = 90.0f,
                    VariantType = "VT_R4",
                },
            },
        };

        var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxEvent.BodyOneofCase.OnAlarmTransition, parsed.BodyCase);
        Assert.Equal(AlarmTransitionKind.Acknowledge, parsed.OnAlarmTransition.TransitionKind);
        Assert.Equal(raise, parsed.OnAlarmTransition.OriginalRaiseTimestamp);
        Assert.Equal("operator1", parsed.OnAlarmTransition.OperatorUser);
    }

    /// <summary>Verifies that an OnAlarmTransition event round-trips with only the required fields populated.</summary>
    [Fact]
    public void Event_RoundTripsOnAlarmTransitionWithOptionalFieldsEmpty()
    {
        var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
        var original = new MxEvent
        {
            Family = MxEventFamily.OnAlarmTransition,
            SessionId = "session-1",
            WorkerSequence = 100,
            OnAlarmTransition = new OnAlarmTransitionEvent
            {
                AlarmFullReference = "Tank01.Level.HiHi",
                AlarmTypeName = "AnalogLimitAlarm.HiHi",
                TransitionKind = AlarmTransitionKind.Raise,
                Severity = 750,
                TransitionTimestamp = raise,
            },
        };

        var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(string.Empty, parsed.OnAlarmTransition.OperatorUser);
        Assert.Equal(string.Empty, parsed.OnAlarmTransition.OperatorComment);
        Assert.Null(parsed.OnAlarmTransition.OriginalRaiseTimestamp);
        Assert.Null(parsed.OnAlarmTransition.CurrentValue);
    }

    /// <summary>Verifies that an MxEvent body oneof rejects multiple bodies — last write wins per proto3 semantics.</summary>
    [Fact]
    public void Event_OneofGuard_LastBodyWins()
    {
        var ev = new MxEvent
        {
            Family = MxEventFamily.OnAlarmTransition,
            OnDataChange = new OnDataChangeEvent(),
            OnAlarmTransition = new OnAlarmTransitionEvent
            {
                AlarmFullReference = "X",
                TransitionKind = AlarmTransitionKind.Raise,
            },
        };

        Assert.Equal(MxEvent.BodyOneofCase.OnAlarmTransition, ev.BodyCase);
        Assert.Null(ev.OnDataChange);
    }

    /// <summary>Verifies that AcknowledgeAlarmRequest round-trips through serialization.</summary>
    [Fact]
    public void AcknowledgeAlarmRequest_RoundTripsAllFields()
    {
        var original = new AcknowledgeAlarmRequest
        {
            ClientCorrelationId = "client-correlation-7",
            AlarmFullReference = "Tank01.Level.HiHi",
            Comment = "shift handover",
            OperatorUser = "operator2",
        };

        var parsed = AcknowledgeAlarmRequest.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
    }

    /// <summary>Verifies that AcknowledgeAlarmReply round-trips with status, hresult, and diagnostics.</summary>
    [Fact]
    public void AcknowledgeAlarmReply_RoundTripsStatusAndHresult()
    {
        var original = new AcknowledgeAlarmReply
        {
            CorrelationId = "gateway-correlation-7",
            ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
            Hresult = 0,
            Status = new MxStatusProxy
            {
                Success = 1,
                Category = MxStatusCategory.Ok,
                DetectedBy = MxStatusSource.RespondingLmx,
            },
            DiagnosticMessage = "ack accepted",
        };

        var parsed = AcknowledgeAlarmReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.True(parsed.HasHresult);
    }

    /// <summary>
    ///     Pins the documented command/reply payload-reuse contract: an
    ///     <c>ACKNOWLEDGE_ALARM_BY_NAME</c> command's reply intentionally has no
    ///     by-name-specific payload case and instead reuses the
    ///     <c>acknowledge_alarm</c> (<see cref="AcknowledgeAlarmReplyPayload"/>)
    ///     case. A future change that adds a separate by-name reply case — or
    ///     drops the reuse — breaks this test. See docs/AlarmClientDiscovery.md
    ///     section 4.
    /// </summary>
    [Fact]
    public void MxCommandReply_AcknowledgeAlarmByName_ReusesAcknowledgeAlarmPayloadCase()
    {
        // The reply oneof must NOT have a by-name-specific case. If a future
        // edit adds one, this assertion fails and forces the doc/test contract
        // to be revisited deliberately.
        foreach (MxCommandReply.PayloadOneofCase value in
                 System.Enum.GetValues<MxCommandReply.PayloadOneofCase>())
        {
            Assert.NotEqual("AcknowledgeAlarmByName", value.ToString());
        }

        var original = new MxCommandReply
        {
            SessionId = "session-1",
            CorrelationId = "gateway-correlation-7",
            Kind = MxCommandKind.AcknowledgeAlarmByName,
            ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
            Hresult = 0,
            // By-name ack reuses the acknowledge_alarm payload case; see the
            // worker's MxAccessCommandExecutor.ExecuteAcknowledgeAlarmByName.
            AcknowledgeAlarm = new AcknowledgeAlarmReplyPayload
            {
                NativeStatus = 0,
            },
        };

        var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        // Kind distinguishes the by-name ack; the payload case is shared.
        Assert.Equal(MxCommandKind.AcknowledgeAlarmByName, parsed.Kind);
        Assert.Equal(MxCommandReply.PayloadOneofCase.AcknowledgeAlarm, parsed.PayloadCase);
        Assert.Equal(0, parsed.AcknowledgeAlarm.NativeStatus);
        // The by-name command has its own command payload case — the asymmetry
        // with the reply oneof is the documented contract under test.
        Assert.Contains(
            MxCommand.PayloadOneofCase.AcknowledgeAlarmByNameCommand,
            System.Enum.GetValues<MxCommand.PayloadOneofCase>());
    }

    /// <summary>Verifies that ActiveAlarmSnapshot round-trips with current state and operator metadata.</summary>
    [Fact]
    public void ActiveAlarmSnapshot_RoundTripsAllFields()
    {
        var raise = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc));
        var ack = Timestamp.FromDateTime(new DateTime(2026, 5, 1, 12, 0, 30, DateTimeKind.Utc));
        var original = new ActiveAlarmSnapshot
        {
            AlarmFullReference = "Tank01.Level.HiHi",
            SourceObjectReference = "Tank01",
            AlarmTypeName = "AnalogLimitAlarm.HiHi",
            Severity = 750,
            OriginalRaiseTimestamp = raise,
            CurrentState = AlarmConditionState.ActiveAcked,
            Category = "Process",
            Description = "Tank 01 high-high level",
            LastTransitionTimestamp = ack,
            OperatorUser = "operator2",
            OperatorComment = "investigating",
        };

        var parsed = ActiveAlarmSnapshot.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(AlarmConditionState.ActiveAcked, parsed.CurrentState);
    }

    /// <summary>Verifies that StreamAlarmsRequest round-trips with and without a filter prefix.</summary>
    [Fact]
    public void StreamAlarmsRequest_RoundTripsWithAndWithoutFilter()
    {
        var withoutFilter = new StreamAlarmsRequest
        {
            ClientCorrelationId = "client-correlation-8",
        };

        var withFilter = new StreamAlarmsRequest
        {
            ClientCorrelationId = "client-correlation-9",
            AlarmFilterPrefix = "Tank01.",
        };

        Assert.Equal(withoutFilter, StreamAlarmsRequest.Parser.ParseFrom(withoutFilter.ToByteArray()));
        Assert.Equal(withFilter, StreamAlarmsRequest.Parser.ParseFrom(withFilter.ToByteArray()));
    }

    /// <summary>
    ///     Verifies that <c>QueryActiveAlarmsRequest</c> pins the additive-only field numbering
    ///     (<c>session_id = 1</c>, <c>client_correlation_id = 2</c>, <c>alarm_filter_prefix = 3</c>)
    ///     advertised in its proto comment, that the message round-trips with the optional
    ///     <c>alarm_filter_prefix</c> populated (the filter semantic the public RPC comment
    ///     documents), and that <c>QueryActiveAlarms</c> remains on the public service surface.
    /// </summary>
    [Fact]
    public void QueryActiveAlarmsRequest_PinsFieldNumbersAndRoundTripsPrefixFilter()
    {
        var service = Assert.Single(
            MxaccessGatewayReflection.Descriptor.Services,
            descriptor => descriptor.Name == "MxAccessGateway");
        Assert.Contains(service.Methods, method => method.Name == "QueryActiveAlarms");

        var fields = QueryActiveAlarmsRequest.Descriptor.Fields;
        Assert.Equal(1, fields[QueryActiveAlarmsRequest.SessionIdFieldNumber].FieldNumber);
        Assert.Equal(2, fields[QueryActiveAlarmsRequest.ClientCorrelationIdFieldNumber].FieldNumber);
        Assert.Equal(3, fields[QueryActiveAlarmsRequest.AlarmFilterPrefixFieldNumber].FieldNumber);
        Assert.Equal("session_id", fields[QueryActiveAlarmsRequest.SessionIdFieldNumber].Name);
        Assert.Equal("client_correlation_id", fields[QueryActiveAlarmsRequest.ClientCorrelationIdFieldNumber].Name);
        Assert.Equal("alarm_filter_prefix", fields[QueryActiveAlarmsRequest.AlarmFilterPrefixFieldNumber].Name);

        var withoutFilter = new QueryActiveAlarmsRequest
        {
            ClientCorrelationId = "client-correlation-10",
        };

        var withFilter = new QueryActiveAlarmsRequest
        {
            ClientCorrelationId = "client-correlation-11",
            AlarmFilterPrefix = "Tank01.",
        };

        Assert.Equal(withoutFilter, QueryActiveAlarmsRequest.Parser.ParseFrom(withoutFilter.ToByteArray()));
        var parsedWithFilter = QueryActiveAlarmsRequest.Parser.ParseFrom(withFilter.ToByteArray());
        Assert.Equal(withFilter, parsedWithFilter);
        Assert.Equal("Tank01.", parsedWithFilter.AlarmFilterPrefix);
    }

    /// <summary>Verifies that an MxValue carrying a raw_value bytes payload round-trips.</summary>
    [Fact]
    public void MxValue_RoundTripsRawValueBytesPayload()
    {
        var original = new MxValue
        {
            DataType = MxDataType.Unknown,
            VariantType = "VT_UNKNOWN",
            RawDataType = 99,
            RawDiagnostic = "uninterpreted COM variant",
            RawValue = ByteString.CopyFrom(0x01, 0x02, 0xFE, 0xFF),
        };

        var parsed = MxValue.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxValue.KindOneofCase.RawValue, parsed.KindCase);
        Assert.Equal(new byte[] { 0x01, 0x02, 0xFE, 0xFF }, parsed.RawValue.ToByteArray());
    }

    /// <summary>Verifies that an MxArray carrying a RawArray of byte blobs round-trips.</summary>
    [Fact]
    public void MxArray_RoundTripsRawArrayPayload()
    {
        var original = new MxArray
        {
            ElementDataType = MxDataType.Unknown,
            VariantType = "VT_ARRAY|VT_UNKNOWN",
            RawElementDataType = 99,
            RawDiagnostic = "uninterpreted SAFEARRAY",
            Dimensions = { 2 },
            RawValues = new RawArray
            {
                Values =
                {
                    ByteString.CopyFrom(0xAA, 0xBB),
                    ByteString.CopyFrom(0xCC, 0xDD),
                },
            },
        };

        var parsed = MxArray.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxArray.ValuesOneofCase.RawValues, parsed.ValuesCase);
        Assert.Equal(2, parsed.RawValues.Values.Count);
    }

    /// <summary>Verifies that a BulkSubscribeReply with per-item SubscribeResults round-trips.</summary>
    [Fact]
    public void BulkSubscribeReply_RoundTripsSubscribeResults()
    {
        var original = new BulkSubscribeReply
        {
            Results =
            {
                new SubscribeResult
                {
                    ServerHandle = 10,
                    TagAddress = "Provider!Tank01.Level",
                    ItemHandle = 21,
                    WasSuccessful = true,
                },
                new SubscribeResult
                {
                    ServerHandle = 10,
                    TagAddress = "Provider!Bad.Tag",
                    WasSuccessful = false,
                    ErrorMessage = "item not found",
                },
            },
        };

        var parsed = BulkSubscribeReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(2, parsed.Results.Count);
        Assert.True(parsed.Results[0].WasSuccessful);
        Assert.False(parsed.Results[1].WasSuccessful);
    }

    /// <summary>Verifies that a bulk-subscribe command and its BulkSubscribeReply payload round-trip.</summary>
    [Fact]
    public void MxCommandReply_RoundTripsBulkSubscribePayload()
    {
        var original = new MxCommandReply
        {
            SessionId = "session-1",
            CorrelationId = "gateway-correlation-bulk",
            Kind = MxCommandKind.SubscribeBulk,
            ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
            Hresult = 0,
            SubscribeBulk = new BulkSubscribeReply
            {
                Results =
                {
                    new SubscribeResult
                    {
                        ServerHandle = 5,
                        TagAddress = "Provider!Tank01.Level",
                        ItemHandle = 7,
                        WasSuccessful = true,
                    },
                },
            },
        };

        var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommandReply.PayloadOneofCase.SubscribeBulk, parsed.PayloadCase);
        Assert.Single(parsed.SubscribeBulk.Results);
    }

    /// <summary>Verifies that a WorkerEnvelope carrying a WorkerFault body round-trips.</summary>
    [Fact]
    public void WorkerEnvelope_RoundTripsWorkerFaultBody()
    {
        var original = new WorkerEnvelope
        {
            ProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
            SessionId = "session-1",
            Sequence = 11,
            CorrelationId = "gateway-correlation-fault",
            WorkerFault = new WorkerFault
            {
                Category = WorkerFaultCategory.MxaccessCommandFailed,
                CommandMethod = "Register",
                Hresult = unchecked((int)0x80004005),
                ExceptionType = "System.Runtime.InteropServices.COMException",
                DiagnosticMessage = "MXAccess COM call failed.",
                ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.ProtocolViolation },
            },
        };

        var parsed = WorkerEnvelope.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerFault, parsed.BodyCase);
        Assert.True(parsed.WorkerFault.HasHresult);
    }

    /// <summary>Verifies that a WorkerEnvelope carrying a WorkerHeartbeat body round-trips.</summary>
    [Fact]
    public void WorkerEnvelope_RoundTripsWorkerHeartbeatBody()
    {
        var activity = Timestamp.FromDateTime(new DateTime(2026, 5, 18, 9, 0, 0, DateTimeKind.Utc));
        var original = new WorkerEnvelope
        {
            ProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
            SessionId = "session-1",
            Sequence = 12,
            CorrelationId = "gateway-correlation-heartbeat",
            WorkerHeartbeat = new WorkerHeartbeat
            {
                WorkerProcessId = 4242,
                State = WorkerState.Ready,
                LastStaActivityTimestamp = activity,
                PendingCommandCount = 3,
                OutboundEventQueueDepth = 7,
                LastEventSequence = 1234,
                CurrentCommandCorrelationId = "in-flight-1",
            },
        };

        var parsed = WorkerEnvelope.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerHeartbeat, parsed.BodyCase);
        Assert.Equal(WorkerState.Ready, parsed.WorkerHeartbeat.State);
    }

    /// <summary>Verifies that the Galaxy Repository service descriptor exposes its browse RPCs.</summary>
    [Fact]
    public void GalaxyRepositoryDescriptor_ContainsBrowseServiceMethods()
    {
        var service = Assert.Single(
            GalaxyRepositoryReflection.Descriptor.Services,
            descriptor => descriptor.Name == "GalaxyRepository");

        Assert.Contains(service.Methods, method => method.Name == "TestConnection");
        Assert.Contains(service.Methods, method => method.Name == "GetLastDeployTime");
        Assert.Contains(service.Methods, method => method.Name == "DiscoverHierarchy");
        Assert.Contains(service.Methods, method => method.Name == "WatchDeployEvents");
        Assert.Contains(service.Methods, method => method.Name == "BrowseChildren");
    }

    /// <summary>
    ///     Verifies that a DiscoverHierarchyRequest round-trips through every
    ///     <c>root</c> oneof arm and its proto wrapper-typed <c>max_depth</c> field.
    /// </summary>
    /// <param name="rootArm">The oneof arm selector (0=RootGobjectId, 1=RootTagName, 2=RootContainedPath).</param>
    [Theory]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(2)]
    public void DiscoverHierarchyRequest_RoundTripsRootOneofAndWrapperFields(int rootArm)
    {
        var original = new DiscoverHierarchyRequest
        {
            PageSize = 100,
            PageToken = "page-2",
            MaxDepth = 5,
            CategoryIds = { 3, 9 },
            TemplateChainContains = { "Analog", "Pump" },
            TagNameGlob = "Tank*",
            IncludeAttributes = true,
            AlarmBearingOnly = true,
            HistorizedOnly = false,
        };
        switch (rootArm)
        {
            case 0:
                original.RootGobjectId = 4711;
                break;
            case 1:
                original.RootTagName = "Tank01";
                break;
            default:
                original.RootContainedPath = "Area1.Tank01";
                break;
        }

        var parsed = DiscoverHierarchyRequest.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(original.RootCase, parsed.RootCase);
        Assert.NotEqual(DiscoverHierarchyRequest.RootOneofCase.None, parsed.RootCase);
        Assert.NotNull(parsed.MaxDepth);
        Assert.Equal(5, parsed.MaxDepth!.Value);
        Assert.True(parsed.HasIncludeAttributes);
        Assert.True(parsed.IncludeAttributes);
    }

    /// <summary>
    ///     Verifies that a DiscoverHierarchyReply round-trips with nested
    ///     GalaxyObject and GalaxyAttribute graphs.
    /// </summary>
    [Fact]
    public void DiscoverHierarchyReply_RoundTripsObjectAndAttributeGraph()
    {
        var original = new DiscoverHierarchyReply
        {
            NextPageToken = "page-3",
            TotalObjectCount = 2,
            Objects =
            {
                new GalaxyObject
                {
                    GobjectId = 4711,
                    TagName = "Tank01",
                    ContainedName = "Tank01",
                    BrowseName = "Tank 01",
                    ParentGobjectId = 12,
                    IsArea = false,
                    CategoryId = 3,
                    HostedByGobjectId = 8,
                    TemplateChain = { "$AnalogDevice", "$Tank" },
                    Attributes =
                    {
                        new GalaxyAttribute
                        {
                            AttributeName = "Level",
                            FullTagReference = "Galaxy!Tank01.Level",
                            MxDataType = 3,
                            DataTypeName = "Float",
                            IsArray = false,
                            ArrayDimension = 0,
                            ArrayDimensionPresent = false,
                            MxAttributeCategory = 1,
                            SecurityClassification = 0,
                            IsHistorized = true,
                            IsAlarm = true,
                        },
                    },
                },
                new GalaxyObject
                {
                    GobjectId = 12,
                    TagName = "Area1",
                    IsArea = true,
                },
            },
        };

        var parsed = DiscoverHierarchyReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(2, parsed.Objects.Count);
        Assert.Single(parsed.Objects[0].Attributes);
        Assert.True(parsed.Objects[0].Attributes[0].IsAlarm);
    }

    /// <summary>
    ///     Verifies that a BrowseChildrenRequest round-trips through every
    ///     <c>parent</c> oneof arm with the full filter set populated.
    /// </summary>
    /// <param name="parentArm">The oneof arm selector (0=ParentGobjectId, 1=ParentTagName, 2=ParentContainedPath).</param>
    [Theory]
    [InlineData(0)]
    [InlineData(1)]
    [InlineData(2)]
    public void BrowseChildrenRequest_RoundTripsParentOneofAndFilters(int parentArm)
    {
        var original = new BrowseChildrenRequest
        {
            PageSize = 200,
            PageToken = "opaque-2",
            CategoryIds = { 3, 9 },
            TemplateChainContains = { "Analog", "Pump" },
            TagNameGlob = "Tank*",
            IncludeAttributes = true,
            AlarmBearingOnly = true,
            HistorizedOnly = false,
        };
        switch (parentArm)
        {
            case 0:
                original.ParentGobjectId = 4711;
                break;
            case 1:
                original.ParentTagName = "Tank01";
                break;
            default:
                original.ParentContainedPath = "Area1.Tank01";
                break;
        }

        var parsed = BrowseChildrenRequest.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(original.ParentCase, parsed.ParentCase);
        Assert.NotEqual(BrowseChildrenRequest.ParentOneofCase.None, parsed.ParentCase);
        Assert.True(parsed.HasIncludeAttributes);
        Assert.True(parsed.IncludeAttributes);
    }

    /// <summary>
    ///     Verifies that a BrowseChildrenReply round-trips its children list,
    ///     the parallel-indexed <c>child_has_children</c> array, and the
    ///     cache sequence used to bind page tokens.
    /// </summary>
    [Fact]
    public void BrowseChildrenReply_RoundTripsChildrenAndHasChildrenParallelArrays()
    {
        var original = new BrowseChildrenReply
        {
            NextPageToken = "opaque-3",
            TotalChildCount = 2,
            CacheSequence = 42UL,
            Children =
            {
                new GalaxyObject
                {
                    GobjectId = 4711,
                    TagName = "Tank01",
                    ContainedName = "Tank01",
                    BrowseName = "Tank 01",
                    ParentGobjectId = 12,
                    IsArea = false,
                    CategoryId = 3,
                    HostedByGobjectId = 8,
                    TemplateChain = { "$AnalogDevice", "$Tank" },
                    Attributes =
                    {
                        new GalaxyAttribute
                        {
                            AttributeName = "Level",
                            FullTagReference = "Galaxy!Tank01.Level",
                            MxDataType = 3,
                            DataTypeName = "Float",
                            IsArray = false,
                            ArrayDimension = 0,
                            ArrayDimensionPresent = false,
                            MxAttributeCategory = 1,
                            SecurityClassification = 0,
                            IsHistorized = true,
                            IsAlarm = true,
                        },
                    },
                },
                new GalaxyObject
                {
                    GobjectId = 12,
                    TagName = "Area1",
                    IsArea = true,
                },
            },
            ChildHasChildren = { true, false },
        };

        var parsed = BrowseChildrenReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(2, parsed.Children.Count);
        Assert.Equal(2, parsed.ChildHasChildren.Count);
        Assert.True(parsed.ChildHasChildren[0]);
        Assert.False(parsed.ChildHasChildren[1]);
        Assert.Equal(42UL, parsed.CacheSequence);
    }

    /// <summary>Verifies that a DeployEvent round-trips with its timestamp and counters.</summary>
    [Fact]
    public void DeployEvent_RoundTripsTimestampAndCounters()
    {
        var observed = Timestamp.FromDateTime(new DateTime(2026, 5, 18, 8, 30, 0, DateTimeKind.Utc));
        var deploy = Timestamp.FromDateTime(new DateTime(2026, 5, 18, 8, 0, 0, DateTimeKind.Utc));
        var original = new DeployEvent
        {
            Sequence = 17,
            ObservedAt = observed,
            TimeOfLastDeploy = deploy,
            TimeOfLastDeployPresent = true,
            ObjectCount = 240,
            AttributeCount = 3600,
        };

        var parsed = DeployEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.True(parsed.TimeOfLastDeployPresent);
        Assert.Equal(deploy, parsed.TimeOfLastDeploy);
    }

    /// <summary>Verifies that GetLastDeployTimeReply and TestConnectionReply round-trip.</summary>
    [Fact]
    public void GalaxyConnectionReplies_RoundTrip()
    {
        var deploy = Timestamp.FromDateTime(new DateTime(2026, 5, 18, 8, 0, 0, DateTimeKind.Utc));
        var lastDeploy = new GetLastDeployTimeReply
        {
            Present = true,
            TimeOfLastDeploy = deploy,
        };
        var testConnection = new TestConnectionReply { Ok = true };

        Assert.Equal(lastDeploy, GetLastDeployTimeReply.Parser.ParseFrom(lastDeploy.ToByteArray()));
        Assert.Equal(testConnection, TestConnectionReply.Parser.ParseFrom(testConnection.ToByteArray()));
    }

    /// <summary>
    ///     Verifies that a <see cref="WriteBulkCommand"/> carrying multiple
    ///     <see cref="WriteBulkEntry"/> items round-trips, including the
    ///     per-entry <c>value</c> and <c>user_id</c> fields.
    /// </summary>
    [Fact]
    public void WriteBulkCommand_RoundTripsEntries()
    {
        var original = new MxCommand
        {
            Kind = MxCommandKind.WriteBulk,
            WriteBulk = new WriteBulkCommand
            {
                ServerHandle = 10,
                Entries =
                {
                    new WriteBulkEntry
                    {
                        ItemHandle = 21,
                        UserId = 7,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 1.25f,
                            VariantType = "VT_R4",
                        },
                    },
                    new WriteBulkEntry
                    {
                        ItemHandle = 22,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Integer,
                            Int32Value = 42,
                            VariantType = "VT_I4",
                        },
                    },
                },
            },
        };

        var parsed = MxCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.WriteBulk, parsed.PayloadCase);
        Assert.Equal(2, parsed.WriteBulk.Entries.Count);
        Assert.Equal(7, parsed.WriteBulk.Entries[0].UserId);
    }

    /// <summary>
    ///     Verifies that a <see cref="Write2BulkCommand"/> round-trips, including
    ///     the per-entry <c>timestamp_value</c> field that distinguishes Write2
    ///     from Write.
    /// </summary>
    [Fact]
    public void Write2BulkCommand_RoundTripsEntriesWithTimestampValue()
    {
        var timestamp = Timestamp.FromDateTime(new DateTime(2026, 5, 19, 11, 0, 0, DateTimeKind.Utc));
        var original = new MxCommand
        {
            Kind = MxCommandKind.Write2Bulk,
            Write2Bulk = new Write2BulkCommand
            {
                ServerHandle = 10,
                Entries =
                {
                    new Write2BulkEntry
                    {
                        ItemHandle = 21,
                        UserId = 7,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 99.9f,
                            VariantType = "VT_R4",
                        },
                        TimestampValue = new MxValue
                        {
                            DataType = MxDataType.Time,
                            TimestampValue = timestamp,
                            VariantType = "VT_DATE",
                        },
                    },
                },
            },
        };

        var parsed = MxCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.Write2Bulk, parsed.PayloadCase);
        Assert.NotNull(parsed.Write2Bulk.Entries[0].TimestampValue);
    }

    /// <summary>
    ///     Verifies that a <see cref="WriteSecuredBulkCommand"/> round-trips,
    ///     pinning the credential-bearing entry shape
    ///     (<c>current_user_id</c>, <c>verifier_user_id</c>, <c>value</c>).
    /// </summary>
    [Fact]
    public void WriteSecuredBulkCommand_RoundTripsCredentialBearingEntries()
    {
        var original = new MxCommand
        {
            Kind = MxCommandKind.WriteSecuredBulk,
            WriteSecuredBulk = new WriteSecuredBulkCommand
            {
                ServerHandle = 10,
                Entries =
                {
                    new WriteSecuredBulkEntry
                    {
                        ItemHandle = 21,
                        CurrentUserId = 100,
                        VerifierUserId = 200,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 75.0f,
                            VariantType = "VT_R4",
                        },
                    },
                },
            },
        };

        var parsed = MxCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.WriteSecuredBulk, parsed.PayloadCase);
        Assert.Equal(100, parsed.WriteSecuredBulk.Entries[0].CurrentUserId);
        Assert.Equal(200, parsed.WriteSecuredBulk.Entries[0].VerifierUserId);
    }

    /// <summary>
    ///     Verifies that a <see cref="WriteSecured2BulkCommand"/> round-trips,
    ///     including both the credential-sensitive <c>value</c> and the
    ///     <c>timestamp_value</c> per entry.
    /// </summary>
    [Fact]
    public void WriteSecured2BulkCommand_RoundTripsCredentialBearingEntriesWithTimestamp()
    {
        var timestamp = Timestamp.FromDateTime(new DateTime(2026, 5, 19, 11, 30, 0, DateTimeKind.Utc));
        var original = new MxCommand
        {
            Kind = MxCommandKind.WriteSecured2Bulk,
            WriteSecured2Bulk = new WriteSecured2BulkCommand
            {
                ServerHandle = 10,
                Entries =
                {
                    new WriteSecured2BulkEntry
                    {
                        ItemHandle = 21,
                        CurrentUserId = 100,
                        VerifierUserId = 200,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 50.0f,
                            VariantType = "VT_R4",
                        },
                        TimestampValue = new MxValue
                        {
                            DataType = MxDataType.Time,
                            TimestampValue = timestamp,
                            VariantType = "VT_DATE",
                        },
                    },
                },
            },
        };

        var parsed = MxCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.WriteSecured2Bulk, parsed.PayloadCase);
        Assert.NotNull(parsed.WriteSecured2Bulk.Entries[0].TimestampValue);
    }

    /// <summary>
    ///     Verifies that a <see cref="ReadBulkCommand"/> round-trips, including
    ///     the <c>tag_addresses</c> list and the <c>timeout_ms</c> field that
    ///     distinguishes the cached-vs-snapshot lifecycle.
    /// </summary>
    [Fact]
    public void ReadBulkCommand_RoundTripsTagAddressesAndTimeout()
    {
        var original = new MxCommand
        {
            Kind = MxCommandKind.ReadBulk,
            ReadBulk = new ReadBulkCommand
            {
                ServerHandle = 10,
                TagAddresses = { "Provider!Tank01.Level", "Provider!Tank02.Level" },
                TimeoutMs = 2500,
            },
        };

        var parsed = MxCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommand.PayloadOneofCase.ReadBulk, parsed.PayloadCase);
        Assert.Equal(2, parsed.ReadBulk.TagAddresses.Count);
        Assert.Equal(2500u, parsed.ReadBulk.TimeoutMs);
    }

    /// <summary>
    ///     Verifies that a <see cref="BulkWriteReply"/> carrying mixed-outcome
    ///     <see cref="BulkWriteResult"/> entries round-trips and that the
    ///     proto3 <c>optional int32 hresult</c> presence flag survives both the
    ///     "hresult set" and "hresult unset" cases.
    /// </summary>
    [Fact]
    public void BulkWriteReply_RoundTripsResultsWithOptionalHresultPresence()
    {
        var original = new BulkWriteReply
        {
            Results =
            {
                new BulkWriteResult
                {
                    ServerHandle = 10,
                    ItemHandle = 21,
                    WasSuccessful = true,
                    Hresult = 0,
                    Statuses =
                    {
                        new MxStatusProxy
                        {
                            Success = 1,
                            Category = MxStatusCategory.Ok,
                            DetectedBy = MxStatusSource.RespondingLmx,
                        },
                    },
                },
                new BulkWriteResult
                {
                    ServerHandle = 10,
                    ItemHandle = 22,
                    WasSuccessful = false,
                    Hresult = unchecked((int)0x80004005),
                    ErrorMessage = "item not advised",
                },
                new BulkWriteResult
                {
                    ServerHandle = 10,
                    ItemHandle = 23,
                    WasSuccessful = false,
                    // Hresult deliberately UNSET — exercises the proto3
                    // `optional int32` HasField() = false arm.
                    ErrorMessage = "tag rejected by allowlist",
                },
            },
        };

        var parsed = BulkWriteReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(3, parsed.Results.Count);
        Assert.True(parsed.Results[0].HasHresult);
        Assert.True(parsed.Results[1].HasHresult);
        Assert.False(parsed.Results[2].HasHresult);
        Assert.True(parsed.Results[0].WasSuccessful);
        Assert.False(parsed.Results[2].WasSuccessful);
        Assert.Single(parsed.Results[0].Statuses);
    }

    /// <summary>
    ///     Verifies that a <see cref="BulkReadReply"/> carrying both cached
    ///     (<c>was_cached = true</c>) and uncached (<c>was_cached = false</c>)
    ///     <see cref="BulkReadResult"/> entries round-trips. Pins the
    ///     deliberate absence of <c>hresult</c> on <see cref="BulkReadResult"/>
    ///     — failures are carried as <c>was_successful = false</c> plus
    ///     <c>error_message</c> only.
    /// </summary>
    [Fact]
    public void BulkReadReply_RoundTripsCachedAndSnapshotResults()
    {
        var sourceTimestamp = Timestamp.FromDateTime(new DateTime(2026, 5, 19, 12, 0, 0, DateTimeKind.Utc));
        var original = new BulkReadReply
        {
            Results =
            {
                new BulkReadResult
                {
                    ServerHandle = 10,
                    TagAddress = "Provider!Tank01.Level",
                    ItemHandle = 21,
                    WasSuccessful = true,
                    WasCached = true,
                    Value = new MxValue
                    {
                        DataType = MxDataType.Float,
                        FloatValue = 42.5f,
                        VariantType = "VT_R4",
                    },
                    Quality = 192,
                    SourceTimestamp = sourceTimestamp,
                    Statuses =
                    {
                        new MxStatusProxy
                        {
                            Success = 1,
                            Category = MxStatusCategory.Ok,
                            DetectedBy = MxStatusSource.RespondingNmx,
                        },
                    },
                },
                new BulkReadResult
                {
                    ServerHandle = 10,
                    TagAddress = "Provider!Tank02.Level",
                    ItemHandle = 22,
                    WasSuccessful = true,
                    WasCached = false,
                    Value = new MxValue
                    {
                        DataType = MxDataType.Integer,
                        Int32Value = 0,
                        VariantType = "VT_I4",
                    },
                    SourceTimestamp = sourceTimestamp,
                },
                new BulkReadResult
                {
                    ServerHandle = 10,
                    TagAddress = "Provider!Bad.Tag",
                    WasSuccessful = false,
                    WasCached = false,
                    ErrorMessage = "snapshot timed out before first OnDataChange",
                },
            },
        };

        var parsed = BulkReadReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(3, parsed.Results.Count);
        Assert.True(parsed.Results[0].WasCached);
        Assert.False(parsed.Results[1].WasCached);
        Assert.False(parsed.Results[2].WasSuccessful);
        Assert.Equal("snapshot timed out before first OnDataChange", parsed.Results[2].ErrorMessage);
        // BulkReadResult has no `hresult` field — pin that contract.
        Assert.DoesNotContain(
            BulkReadResult.Descriptor.Fields.InDeclarationOrder(),
            field => field.Name == "hresult");
    }

    /// <summary>
    ///     Verifies that an <see cref="MxCommandReply"/> with each of the new
    ///     bulk write/read payload oneof cases round-trips and that
    ///     <see cref="MxCommandReply.PayloadOneofCase"/> resolves to the
    ///     expected value. Pins every new oneof case added by the bulk
    ///     write/read extension.
    /// </summary>
    /// <param name="kind">The command kind to test.</param>
    /// <param name="expectedPayloadCase">The expected payload oneof case.</param>
    [Theory]
    [InlineData(MxCommandKind.WriteBulk, MxCommandReply.PayloadOneofCase.WriteBulk)]
    [InlineData(MxCommandKind.Write2Bulk, MxCommandReply.PayloadOneofCase.Write2Bulk)]
    [InlineData(MxCommandKind.WriteSecuredBulk, MxCommandReply.PayloadOneofCase.WriteSecuredBulk)]
    [InlineData(MxCommandKind.WriteSecured2Bulk, MxCommandReply.PayloadOneofCase.WriteSecured2Bulk)]
    public void MxCommandReply_RoundTripsBulkWritePayloadCases(
        MxCommandKind kind,
        MxCommandReply.PayloadOneofCase expectedPayloadCase)
    {
        var reply = new BulkWriteReply
        {
            Results =
            {
                new BulkWriteResult
                {
                    ServerHandle = 5,
                    ItemHandle = 7,
                    WasSuccessful = true,
                    Hresult = 0,
                },
            },
        };
        var original = new MxCommandReply
        {
            SessionId = "session-1",
            CorrelationId = "gateway-correlation-bulk-write",
            Kind = kind,
            ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
            Hresult = 0,
        };
        switch (expectedPayloadCase)
        {
            case MxCommandReply.PayloadOneofCase.WriteBulk:
                original.WriteBulk = reply;
                break;
            case MxCommandReply.PayloadOneofCase.Write2Bulk:
                original.Write2Bulk = reply;
                break;
            case MxCommandReply.PayloadOneofCase.WriteSecuredBulk:
                original.WriteSecuredBulk = reply;
                break;
            case MxCommandReply.PayloadOneofCase.WriteSecured2Bulk:
                original.WriteSecured2Bulk = reply;
                break;
            default:
                throw new System.ArgumentOutOfRangeException(nameof(expectedPayloadCase));
        }

        var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(expectedPayloadCase, parsed.PayloadCase);
        Assert.Equal(kind, parsed.Kind);
    }

    /// <summary>
    ///     Verifies that an <see cref="AlarmFeedMessage"/> carrying the
    ///     <c>provider_status</c> payload case round-trips and resolves to
    ///     <see cref="AlarmFeedMessage.PayloadOneofCase.ProviderStatus"/>.
    /// </summary>
    [Fact]
    public void Feed_RoundTripsProviderStatus()
    {
        var since = Timestamp.FromDateTime(new DateTime(2026, 6, 13, 9, 0, 0, DateTimeKind.Utc));
        var original = new AlarmFeedMessage
        {
            ProviderStatus = new AlarmProviderStatus
            {
                Mode = AlarmProviderMode.Subtag,
                Degraded = true,
                Reason = "wnwrap poll failed 3x (HRESULT 0x80004005)",
                Since = since,
            },
        };

        var parsed = AlarmFeedMessage.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(AlarmFeedMessage.PayloadOneofCase.ProviderStatus, parsed.PayloadCase);
        Assert.True(parsed.ProviderStatus.Degraded);
        Assert.Equal(AlarmProviderMode.Subtag, parsed.ProviderStatus.Mode);
    }

    /// <summary>
    ///     Verifies that an <see cref="OnAlarmTransitionEvent"/> carrying the
    ///     new <c>degraded</c> and <c>source_provider</c> provenance fields
    ///     round-trips with their values preserved.
    /// </summary>
    [Fact]
    public void Transition_RoundTripsDegradedProvenance()
    {
        var t = new OnAlarmTransitionEvent
        {
            AlarmFullReference = "Galaxy!Area.Tank01.Level.HiHi",
            TransitionKind = AlarmTransitionKind.Raise,
            Degraded = true,
            SourceProvider = AlarmProviderMode.Subtag,
        };

        var parsed = OnAlarmTransitionEvent.Parser.ParseFrom(t.ToByteArray());

        Assert.True(parsed.Degraded);
        Assert.Equal(AlarmProviderMode.Subtag, parsed.SourceProvider);
    }

    /// <summary>
    ///     Verifies that an <see cref="MxCommandReply"/> with kind
    ///     <see cref="MxCommandKind.ReadBulk"/> and a populated
    ///     <see cref="BulkReadReply"/> payload round-trips and resolves to
    ///     <see cref="MxCommandReply.PayloadOneofCase.ReadBulk"/>.
    /// </summary>
    [Fact]
    public void MxCommandReply_RoundTripsReadBulkPayload()
    {
        var original = new MxCommandReply
        {
            SessionId = "session-1",
            CorrelationId = "gateway-correlation-read-bulk",
            Kind = MxCommandKind.ReadBulk,
            ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
            Hresult = 0,
            ReadBulk = new BulkReadReply
            {
                Results =
                {
                    new BulkReadResult
                    {
                        ServerHandle = 5,
                        TagAddress = "Provider!Tank01.Level",
                        ItemHandle = 7,
                        WasSuccessful = true,
                        WasCached = true,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 12.5f,
                            VariantType = "VT_R4",
                        },
                    },
                },
            },
        };

        var parsed = MxCommandReply.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxCommandReply.PayloadOneofCase.ReadBulk, parsed.PayloadCase);
        Assert.Single(parsed.ReadBulk.Results);
        Assert.True(parsed.ReadBulk.Results[0].WasCached);
    }

    /// <summary>
    ///     Verifies that an <see cref="ActiveAlarmSnapshot"/> carrying the
    ///     alarm-provider provenance fields <c>degraded</c> (14) and
    ///     <c>source_provider</c> (15) round-trips with their values preserved,
    ///     pinning the wire shape of the byte-identical provenance fields that
    ///     also appear on <see cref="OnAlarmTransitionEvent"/>.
    /// </summary>
    [Fact]
    public void ActiveAlarmSnapshot_RoundTripsDegradedProvenance()
    {
        var raise = Timestamp.FromDateTime(new DateTime(2026, 6, 13, 12, 0, 0, DateTimeKind.Utc));
        var original = new ActiveAlarmSnapshot
        {
            AlarmFullReference = "Galaxy!Area.Tank01.Level.HiHi",
            SourceObjectReference = "Tank01",
            AlarmTypeName = "AnalogLimitAlarm.HiHi",
            Severity = 750,
            OriginalRaiseTimestamp = raise,
            CurrentState = AlarmConditionState.Active,
            Degraded = true,
            SourceProvider = AlarmProviderMode.Subtag,
        };

        var parsed = ActiveAlarmSnapshot.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.True(parsed.Degraded);
        Assert.Equal(AlarmProviderMode.Subtag, parsed.SourceProvider);
    }

    /// <summary>
    ///     Verifies that a <see cref="SubscribeAlarmsCommand"/> populating the
    ///     alarm-provider fallback extensions — <c>forced_mode</c> (2), a
    ///     <c>watch_list</c> entry with all six <see cref="AlarmSubtagTarget"/>
    ///     string fields (3), and a <c>failover</c>
    ///     <see cref="AlarmFailoverConfig"/> (4) — round-trips end to end,
    ///     pinning the wire shape that the forced-subtag-mode fix depends on.
    /// </summary>
    [Fact]
    public void SubscribeAlarmsCommand_RoundTripsForcedModeWatchListAndFailover()
    {
        var original = new SubscribeAlarmsCommand
        {
            SubscriptionExpression = @"\\node\Galaxy!Area",
            ForcedMode = AlarmProviderMode.Subtag,
            WatchList =
            {
                new AlarmSubtagTarget
                {
                    AlarmFullReference = "Galaxy!Area.Tank01.Level.HiHi",
                    SourceObjectReference = "Tank01",
                    ActiveSubtag = "Tank01.Level.HiHi.InAlarm",
                    AckedSubtag = "Tank01.Level.HiHi.Acked",
                    AckCommentSubtag = "Tank01.Level.HiHi.AckMsg",
                    PrioritySubtag = "Tank01.Level.HiHi.Priority",
                },
            },
            Failover = new AlarmFailoverConfig
            {
                ConsecutiveFailureThreshold = 3,
                FailbackProbeIntervalSeconds = 10,
                FailbackStableProbes = 5,
            },
        };

        var parsed = SubscribeAlarmsCommand.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(AlarmProviderMode.Subtag, parsed.ForcedMode);
        var target = Assert.Single(parsed.WatchList);
        Assert.Equal("Galaxy!Area.Tank01.Level.HiHi", target.AlarmFullReference);
        Assert.Equal("Tank01", target.SourceObjectReference);
        Assert.Equal("Tank01.Level.HiHi.InAlarm", target.ActiveSubtag);
        Assert.Equal("Tank01.Level.HiHi.Acked", target.AckedSubtag);
        Assert.Equal("Tank01.Level.HiHi.AckMsg", target.AckCommentSubtag);
        Assert.Equal("Tank01.Level.HiHi.Priority", target.PrioritySubtag);
        Assert.Equal(3, parsed.Failover.ConsecutiveFailureThreshold);
        Assert.Equal(10, parsed.Failover.FailbackProbeIntervalSeconds);
        Assert.Equal(5, parsed.Failover.FailbackStableProbes);
    }

    /// <summary>
    ///     Verifies that an <see cref="MxEvent"/> carrying an
    ///     <see cref="OnAlarmProviderModeChangedEvent"/> body (the
    ///     <c>MxEvent.body</c> oneof tag 25 paired with
    ///     <see cref="MxEventFamily.OnAlarmProviderModeChanged"/>, family 6)
    ///     round-trips and resolves to
    ///     <see cref="MxEvent.BodyOneofCase.OnAlarmProviderModeChanged"/>.
    /// </summary>
    [Fact]
    public void MxEvent_RoundTripsOnAlarmProviderModeChangedBody()
    {
        var at = Timestamp.FromDateTime(new DateTime(2026, 6, 13, 9, 30, 0, DateTimeKind.Utc));
        var original = new MxEvent
        {
            Family = MxEventFamily.OnAlarmProviderModeChanged,
            SessionId = "session-1",
            WorkerSequence = 42,
            OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent
            {
                Mode = AlarmProviderMode.Subtag,
                Reason = "wnwrap poll failed 3x",
                Hresult = unchecked((int)0x80004005),
                At = at,
            },
        };

        var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxEvent.BodyOneofCase.OnAlarmProviderModeChanged, parsed.BodyCase);
        Assert.Equal(MxEventFamily.OnAlarmProviderModeChanged, parsed.Family);
        Assert.Equal(AlarmProviderMode.Subtag, parsed.OnAlarmProviderModeChanged.Mode);
        Assert.Equal(unchecked((int)0x80004005), parsed.OnAlarmProviderModeChanged.Hresult);
    }

    /// <summary>
    ///     Verifies that an <see cref="MxEvent"/> carrying a
    ///     <see cref="ReplayGap"/> (the <c>optional replay_gap = 14</c> field)
    ///     round-trips with both sequence fields populated, that
    ///     <see cref="MxEvent.BodyCase"/> remains <see cref="MxEvent.BodyOneofCase.None"/>
    ///     (replay_gap is not part of the body oneof), and pins the wire field
    ///     numbers for <c>MxEvent.replay_gap</c> (14),
    ///     <c>ReplayGap.requested_after_sequence</c> (1), and
    ///     <c>ReplayGap.oldest_available_sequence</c> (2) via the descriptor.
    /// </summary>
    [Fact]
    public void MxEvent_RoundTripsReplayGapSentinelAndPinsFieldNumbers()
    {
        // ReplayGap field on MxEvent must be wire number 14.
        Assert.Equal(14, MxEvent.ReplayGapFieldNumber);

        // ReplayGap sub-field numbers must be pinned.
        var replayGapFields = ReplayGap.Descriptor.Fields;
        Assert.Equal(1, replayGapFields[ReplayGap.RequestedAfterSequenceFieldNumber].FieldNumber);
        Assert.Equal("requested_after_sequence", replayGapFields[ReplayGap.RequestedAfterSequenceFieldNumber].Name);
        Assert.Equal(2, replayGapFields[ReplayGap.OldestAvailableSequenceFieldNumber].FieldNumber);
        Assert.Equal("oldest_available_sequence", replayGapFields[ReplayGap.OldestAvailableSequenceFieldNumber].Name);

        // Build a sentinel MxEvent: replay_gap set, body oneof unset, family UNSPECIFIED.
        var original = new MxEvent
        {
            SessionId = "session-1",
            WorkerSequence = 0,
            ReplayGap = new ReplayGap
            {
                RequestedAfterSequence = 150,
                OldestAvailableSequence = 200,
            },
        };

        var parsed = MxEvent.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        // replay_gap is NOT part of the body oneof — BodyCase must remain None.
        Assert.Equal(MxEvent.BodyOneofCase.None, parsed.BodyCase);
        Assert.NotNull(parsed.ReplayGap);
        Assert.Equal(150UL, parsed.ReplayGap.RequestedAfterSequence);
        Assert.Equal(200UL, parsed.ReplayGap.OldestAvailableSequence);
    }

    /// <summary>
    ///     Pins the wire field number for <c>MxValue.sparse_array_value</c> (19)
    ///     via the generated constant, pins the <see cref="MxSparseArray"/> and
    ///     <see cref="MxSparseElement"/> field numbers by name + number against the
    ///     descriptor, and round-trips an <see cref="MxValue"/> carrying a
    ///     <see cref="MxSparseArray"/> with at least one <see cref="MxSparseElement"/>
    ///     so a future renumber or type-narrowing is caught at the contract level.
    ///     Also verifies that an all-defaults <see cref="MxSparseArray"/> (no elements)
    ///     is not a proto-level error.
    /// </summary>
    [Fact]
    public void MxValue_RoundTripsSparseArrayValueAndPinsFieldNumbers()
    {
        // Pin MxValue.sparse_array_value wire field number == 19.
        Assert.Equal(19, MxValue.SparseArrayValueFieldNumber);

        // Pin MxSparseArray field numbers by name + number.
        var sparseArrayFields = MxSparseArray.Descriptor.Fields;
        Assert.Equal(1, sparseArrayFields[MxSparseArray.ElementDataTypeFieldNumber].FieldNumber);
        Assert.Equal("element_data_type", sparseArrayFields[MxSparseArray.ElementDataTypeFieldNumber].Name);
        Assert.Equal(2, sparseArrayFields[MxSparseArray.TotalLengthFieldNumber].FieldNumber);
        Assert.Equal("total_length", sparseArrayFields[MxSparseArray.TotalLengthFieldNumber].Name);
        Assert.Equal(3, sparseArrayFields[MxSparseArray.ElementsFieldNumber].FieldNumber);
        Assert.Equal("elements", sparseArrayFields[MxSparseArray.ElementsFieldNumber].Name);

        // Pin MxSparseElement field numbers by name + number.
        var sparseElementFields = MxSparseElement.Descriptor.Fields;
        Assert.Equal(1, sparseElementFields[MxSparseElement.IndexFieldNumber].FieldNumber);
        Assert.Equal("index", sparseElementFields[MxSparseElement.IndexFieldNumber].Name);
        Assert.Equal(2, sparseElementFields[MxSparseElement.ValueFieldNumber].FieldNumber);
        Assert.Equal("value", sparseElementFields[MxSparseElement.ValueFieldNumber].Name);

        // Round-trip an MxValue with a populated MxSparseArray (one scalar element).
        var original = new MxValue
        {
            DataType = MxDataType.Float,
            SparseArrayValue = new MxSparseArray
            {
                ElementDataType = MxDataType.Float,
                TotalLength = 4,
                Elements =
                {
                    new MxSparseElement
                    {
                        Index = 2,
                        Value = new MxValue
                        {
                            DataType = MxDataType.Float,
                            FloatValue = 3.14f,
                            VariantType = "VT_R4",
                        },
                    },
                },
            },
        };

        var parsed = MxValue.Parser.ParseFrom(original.ToByteArray());

        Assert.Equal(original, parsed);
        Assert.Equal(MxValue.KindOneofCase.SparseArrayValue, parsed.KindCase);
        Assert.NotNull(parsed.SparseArrayValue);
        Assert.Equal(MxDataType.Float, parsed.SparseArrayValue.ElementDataType);
        Assert.Equal(4u, parsed.SparseArrayValue.TotalLength);
        var element = Assert.Single(parsed.SparseArrayValue.Elements);
        Assert.Equal(2u, element.Index);
        Assert.Equal(3.14f, element.Value.FloatValue);

        // An all-defaults MxSparseArray (no elements) is not a proto-level error.
        var empty = new MxValue
        {
            DataType = MxDataType.Float,
            SparseArrayValue = new MxSparseArray
            {
                ElementDataType = MxDataType.Float,
                TotalLength = 10,
            },
        };
        var parsedEmpty = MxValue.Parser.ParseFrom(empty.ToByteArray());
        Assert.Equal(empty, parsedEmpty);
        Assert.Equal(MxValue.KindOneofCase.SparseArrayValue, parsedEmpty.KindCase);
        Assert.Empty(parsedEmpty.SparseArrayValue.Elements);
    }
}
