using System.IO;
using FluentAssertions;
using Google.Protobuf;
using ProtoBuf;
using Xunit;
using ProtoGenerated = Scada;
using CodeFirst = ZB.MOM.WW.LmxProxy.Client.Domain;

namespace ZB.MOM.WW.LmxProxy.Client.Tests.CrossStack;

/// <summary>
/// Verifies wire compatibility between Host proto-generated types and Client code-first types.
/// Serializes with one stack, deserializes with the other.
/// </summary>
public class CrossStackSerializationTests
{
    // ── Proto-generated → Code-first ──────────────────────────

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_BoolValue()
    {
        // Arrange: proto-generated VtqMessage with bool TypedValue
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Running",
            Value = new ProtoGenerated.TypedValue { BoolValue = true },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        // Act: serialize with proto, deserialize with protobuf-net
        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        // Assert
        codeFirst.Should().NotBeNull();
        codeFirst.Tag.Should().Be("Motor.Running");
        codeFirst.Value.Should().NotBeNull();
        codeFirst.Value!.BoolValue.Should().BeTrue();
        codeFirst.TimestampUtcTicks.Should().Be(638789000000000000L);
        codeFirst.Quality.Should().NotBeNull();
        codeFirst.Quality!.StatusCode.Should().Be(0x00000000u);
        codeFirst.Quality.SymbolicName.Should().Be("Good");
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_DoubleValue()
    {
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Speed",
            Value = new ProtoGenerated.TypedValue { DoubleValue = 42.5 },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        codeFirst.Value.Should().NotBeNull();
        codeFirst.Value!.DoubleValue.Should().Be(42.5);
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_StringValue()
    {
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Name",
            Value = new ProtoGenerated.TypedValue { StringValue = "Pump A" },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        codeFirst.Value.Should().NotBeNull();
        codeFirst.Value!.StringValue.Should().Be("Pump A");
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_Int32Value()
    {
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Count",
            Value = new ProtoGenerated.TypedValue { Int32Value = 2147483647 },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        codeFirst.Value!.Int32Value.Should().Be(int.MaxValue);
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_BadQuality()
    {
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Fault",
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x806D0000, SymbolicName = "BadSensorFailure" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        codeFirst.Quality!.StatusCode.Should().Be(0x806D0000u);
        codeFirst.Quality.SymbolicName.Should().Be("BadSensorFailure");
        codeFirst.Quality.IsBad.Should().BeTrue();
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_NullValue()
    {
        // No Value field set — represents null
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Optional",
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x80000000, SymbolicName = "Bad" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        // When no oneof is set, the Value object may be null or all-default
        // Either way, GetValueCase() should return None
        if (codeFirst.Value != null)
            codeFirst.Value.GetValueCase().Should().Be(CodeFirst.TypedValueCase.None);
    }

    [Fact]
    public void VtqMessage_ProtoToCodeFirst_FloatArrayValue()
    {
        var floatArr = new ProtoGenerated.FloatArray();
        floatArr.Values.AddRange(new[] { 1.0f, 2.0f, 3.0f });
        var protoMsg = new ProtoGenerated.VtqMessage
        {
            Tag = "Motor.Samples",
            Value = new ProtoGenerated.TypedValue
            {
                ArrayValue = new ProtoGenerated.ArrayValue { FloatValues = floatArr }
            },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new ProtoGenerated.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.VtqMessage>(new MemoryStream(bytes));

        codeFirst.Value.Should().NotBeNull();
        codeFirst.Value!.ArrayValue.Should().NotBeNull();
        codeFirst.Value.ArrayValue!.FloatValues.Should().NotBeNull();
        codeFirst.Value.ArrayValue.FloatValues!.Values.Should().BeEquivalentTo(new[] { 1.0f, 2.0f, 3.0f });
    }

    // ── Code-first → Proto-generated ──────────────────────────

    [Fact]
    public void VtqMessage_CodeFirstToProto_DoubleValue()
    {
        var codeFirst = new CodeFirst.VtqMessage
        {
            Tag = "Motor.Speed",
            Value = new CodeFirst.TypedValue { DoubleValue = 99.9 },
            TimestampUtcTicks = 638789000000000000L,
            Quality = new CodeFirst.QualityCode { StatusCode = 0x00000000, SymbolicName = "Good" }
        };

        // Serialize with protobuf-net
        var ms = new MemoryStream();
        Serializer.Serialize(ms, codeFirst);
        var bytes = ms.ToArray();

        // Deserialize with Google.Protobuf
        var protoMsg = ProtoGenerated.VtqMessage.Parser.ParseFrom(bytes);

        protoMsg.Tag.Should().Be("Motor.Speed");
        protoMsg.Value.Should().NotBeNull();
        protoMsg.Value.DoubleValue.Should().Be(99.9);
        protoMsg.TimestampUtcTicks.Should().Be(638789000000000000L);
        protoMsg.Quality.StatusCode.Should().Be(0x00000000u);
    }

    [Fact]
    public void WriteRequest_CodeFirstToProto()
    {
        var codeFirst = new CodeFirst.WriteRequest
        {
            SessionId = "abc123",
            Tag = "Motor.Speed",
            Value = new CodeFirst.TypedValue { DoubleValue = 42.5 }
        };

        var ms = new MemoryStream();
        Serializer.Serialize(ms, codeFirst);
        var bytes = ms.ToArray();

        var protoMsg = ProtoGenerated.WriteRequest.Parser.ParseFrom(bytes);
        protoMsg.SessionId.Should().Be("abc123");
        protoMsg.Tag.Should().Be("Motor.Speed");
        protoMsg.Value.Should().NotBeNull();
        protoMsg.Value.DoubleValue.Should().Be(42.5);
    }

    [Fact]
    public void ConnectRequest_RoundTrips()
    {
        var codeFirst = new CodeFirst.ConnectRequest { ClientId = "ScadaLink-1", ApiKey = "key-123" };
        var ms = new MemoryStream();
        Serializer.Serialize(ms, codeFirst);
        var protoMsg = ProtoGenerated.ConnectRequest.Parser.ParseFrom(ms.ToArray());
        protoMsg.ClientId.Should().Be("ScadaLink-1");
        protoMsg.ApiKey.Should().Be("key-123");
    }

    [Fact]
    public void ConnectResponse_RoundTrips()
    {
        var protoMsg = new ProtoGenerated.ConnectResponse
        {
            Success = true,
            Message = "Connected",
            SessionId = "abcdef1234567890abcdef1234567890"
        };
        var bytes = protoMsg.ToByteArray();
        var codeFirst = Serializer.Deserialize<CodeFirst.ConnectResponse>(new MemoryStream(bytes));
        codeFirst.Success.Should().BeTrue();
        codeFirst.Message.Should().Be("Connected");
        codeFirst.SessionId.Should().Be("abcdef1234567890abcdef1234567890");
    }

    [Fact]
    public void WriteBatchAndWaitRequest_CodeFirstToProto_TypedFlagValue()
    {
        var codeFirst = new CodeFirst.WriteBatchAndWaitRequest
        {
            SessionId = "sess1",
            FlagTag = "Motor.Done",
            FlagValue = new CodeFirst.TypedValue { BoolValue = true },
            TimeoutMs = 5000,
            PollIntervalMs = 100,
            Items =
            {
                new CodeFirst.WriteItem
                {
                    Tag = "Motor.Speed",
                    Value = new CodeFirst.TypedValue { DoubleValue = 50.0 }
                }
            }
        };

        var ms = new MemoryStream();
        Serializer.Serialize(ms, codeFirst);
        var protoMsg = ProtoGenerated.WriteBatchAndWaitRequest.Parser.ParseFrom(ms.ToArray());

        protoMsg.FlagTag.Should().Be("Motor.Done");
        protoMsg.FlagValue.BoolValue.Should().BeTrue();
        protoMsg.TimeoutMs.Should().Be(5000);
        protoMsg.PollIntervalMs.Should().Be(100);
        protoMsg.Items.Should().HaveCount(1);
        protoMsg.Items[0].Tag.Should().Be("Motor.Speed");
        protoMsg.Items[0].Value.DoubleValue.Should().Be(50.0);
    }
}
