using Google.Protobuf;
using Org.Eclipse.Tahu.Protobuf;
using Shouldly;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests;

/// <summary>
///     Pins the vendored Eclipse Tahu <c>sparkplug_b</c> protobuf codegen: the generated types exist,
///     land in the namespace the rest of P2 is written against, and carry the Sparkplug-B field numbers
///     on the wire. Nothing here decodes a real payload — that is Task 16 (<c>SparkplugCodec</c> +
///     golden vectors); this suite exists so a broken/renamed/mangled vendored proto fails at the
///     codegen seam rather than as a mysterious decode failure three tasks later.
/// </summary>
public sealed class SparkplugProtoCodegenTests
{
    [Fact]
    public void GeneratedPayload_RoundTrips()
    {
        var p = new Payload { Seq = 3 };
        p.Metrics.Add(new Payload.Types.Metric { Name = "Temperature", Alias = 5 });

        var back = Payload.Parser.ParseFrom(p.ToByteArray());

        back.Seq.ShouldBe(3ul);
        back.Metrics[0].Name.ShouldBe("Temperature");
        back.Metrics[0].Alias.ShouldBe(5ul);
    }

    /// <summary>
    ///     Tasks 16 / 20 / 25 name <c>Org.Eclipse.Tahu.Protobuf</c> explicitly, and the namespace is
    ///     derived (not declared) — protoc PascalCases the proto <c>package</c> because the vendored file
    ///     carries no <c>option csharp_namespace</c>. Editing the package line to "tidy" it would silently
    ///     rehome every generated type.
    /// </summary>
    [Fact]
    public void GeneratedTypes_LiveInTheTahuPackageNamespace()
    {
        typeof(Payload).Namespace.ShouldBe("Org.Eclipse.Tahu.Protobuf");
        Payload.Descriptor.File.Package.ShouldBe("org.eclipse.tahu.protobuf");
    }

    /// <summary>
    ///     Field numbers are the wire contract with every Sparkplug edge node in the plant; a vendored
    ///     proto that was hand-edited or reconstructed rather than copied from upstream would most likely
    ///     drift here first.
    /// </summary>
    [Fact]
    public void PayloadAndMetric_CarrySparkplugBFieldNumbers()
    {
        Payload.TimestampFieldNumber.ShouldBe(1);
        Payload.MetricsFieldNumber.ShouldBe(2);
        Payload.SeqFieldNumber.ShouldBe(3);
        Payload.UuidFieldNumber.ShouldBe(4);
        Payload.BodyFieldNumber.ShouldBe(5);

        Payload.Types.Metric.NameFieldNumber.ShouldBe(1);
        Payload.Types.Metric.AliasFieldNumber.ShouldBe(2);
        Payload.Types.Metric.TimestampFieldNumber.ShouldBe(3);
        Payload.Types.Metric.DatatypeFieldNumber.ShouldBe(4);
        Payload.Types.Metric.IsNullFieldNumber.ShouldBe(7);
    }

    /// <summary>
    ///     Task 17's datatype map keys off these indices. They are Sparkplug-spec constants, not protobuf
    ///     tags, so they are worth pinning independently of the field numbers above.
    /// </summary>
    [Fact]
    public void DataTypeEnum_MatchesSparkplugSpecIndices()
    {
        ((int)DataType.Int8).ShouldBe(1);
        ((int)DataType.Int32).ShouldBe(3);
        ((int)DataType.Uint64).ShouldBe(8);
        ((int)DataType.Float).ShouldBe(9);
        ((int)DataType.Double).ShouldBe(10);
        ((int)DataType.Boolean).ShouldBe(11);
        ((int)DataType.String).ShouldBe(12);
        ((int)DataType.DateTime).ShouldBe(13);
        ((int)DataType.DataSet).ShouldBe(16);
        ((int)DataType.Template).ShouldBe(19);
        ((int)DataType.Int32Array).ShouldBe(24);
    }

    /// <summary>
    ///     protoc's C# generator RE-CASES enum member names: the proto's <c>UInt64</c> becomes
    ///     <c>Uint64</c> and <c>UUID</c> becomes <c>Uuid</c> (only the leading character of each
    ///     underscore-separated word survives as a capital). Task 17's datatype map has to spell them
    ///     the mangled way, so pin the mangling rather than letting it be rediscovered as a build error.
    ///     The wire-facing spelling is preserved on <c>OriginalName</c>, which is what
    ///     <c>DataType.Parser</c>-style JSON round-trips use.
    /// </summary>
    [Fact]
    public void DataTypeEnum_CSharpNamesAreProtocMangled_NotTheProtoSpelling()
    {
        Enum.GetNames<DataType>().ShouldContain("Uint64");
        Enum.GetNames<DataType>().ShouldNotContain("UInt64");
        Enum.GetNames<DataType>().ShouldContain("Uuid");
        Enum.GetNames<DataType>().ShouldNotContain("UUID");
    }
}
