using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using Shouldly;
using Xunit;

// See SparkplugTopicTests.cs for why this alias is redeclared locally in every consuming project
// rather than inherited: `SparkplugDataType` is a `global using` alias for the generated
// `Org.Eclipse.Tahu.Protobuf.DataType` (declared in Contracts/SparkplugDataType.cs), and `global using`
// scope does not cross a ProjectReference.
using SparkplugDataType = Org.Eclipse.Tahu.Protobuf.DataType;

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

/// <summary>
///     <see cref="SparkplugDataTypeExtensions.ToDriverDataType"/> / <see cref="SparkplugDataTypeExtensions.IsSparkplugArray"/>
///     coverage (Task 17), per design doc §3.5. <see cref="ToDriverDataType_HandlesEveryGeneratedDataTypeMember_MappedOrExplicitlyUnsupported"/>
///     is the drift guard: because <c>SparkplugDataType</c> is an alias for the vendored proto's
///     generated enum (not a hand-duplicated copy — see the remarks on
///     <see cref="SparkplugDataTypeExtensions"/>), enumerating it enumerates the live generated member
///     set, so a future Eclipse Tahu proto change that adds/removes a <c>DataType</c> member is caught
///     here automatically rather than needing a second enum kept manually in sync.
/// </summary>
public sealed class SparkplugDataTypeTests
{
    /// <summary>Scalar (non-array) datatypes, per the design §3.5 table.</summary>
    [Theory]
    [InlineData(SparkplugDataType.Int8, DriverDataType.Int16)] // widened: no 8-bit DriverDataType member
    [InlineData(SparkplugDataType.Int16, DriverDataType.Int16)]
    [InlineData(SparkplugDataType.Int32, DriverDataType.Int32)]
    [InlineData(SparkplugDataType.Int64, DriverDataType.Int64)]
    [InlineData(SparkplugDataType.Uint8, DriverDataType.UInt16)] // widened
    [InlineData(SparkplugDataType.Uint16, DriverDataType.UInt16)]
    [InlineData(SparkplugDataType.Uint32, DriverDataType.UInt32)]
    [InlineData(SparkplugDataType.Uint64, DriverDataType.UInt64)]
    [InlineData(SparkplugDataType.Float, DriverDataType.Float32)]
    [InlineData(SparkplugDataType.Double, DriverDataType.Float64)] // NOT DriverDataType.Double — no such member
    [InlineData(SparkplugDataType.Boolean, DriverDataType.Boolean)]
    [InlineData(SparkplugDataType.String, DriverDataType.String)]
    [InlineData(SparkplugDataType.DateTime, DriverDataType.DateTime)]
    [InlineData(SparkplugDataType.Text, DriverDataType.String)]
    [InlineData(SparkplugDataType.Uuid, DriverDataType.String)] // generated name for the proto's `UUID`
    [InlineData(SparkplugDataType.Bytes, DriverDataType.String)] // v1 base64/raw fallback
    [InlineData(SparkplugDataType.File, DriverDataType.String)] // v1 base64/raw fallback
    public void ToDriverDataType_MapsScalarTypes(SparkplugDataType s, DriverDataType d)
        => s.ToDriverDataType().ShouldBe(d);

    /// <summary>
    ///     <c>*Array</c> variants map to their scalar element type; <see cref="SparkplugDataTypeExtensions.IsSparkplugArray"/>
    ///     carries the "and it's an array" bit, since <see cref="DriverDataType"/> has no array concept
    ///     of its own (that's an OPC UA ValueRank/ArrayDimensions concern owned further up the stack).
    /// </summary>
    [Theory]
    [InlineData(SparkplugDataType.Int8Array, DriverDataType.Int16)]
    [InlineData(SparkplugDataType.Int16Array, DriverDataType.Int16)]
    [InlineData(SparkplugDataType.Int32Array, DriverDataType.Int32)]
    [InlineData(SparkplugDataType.Int64Array, DriverDataType.Int64)]
    [InlineData(SparkplugDataType.Uint8Array, DriverDataType.UInt16)]
    [InlineData(SparkplugDataType.Uint16Array, DriverDataType.UInt16)]
    [InlineData(SparkplugDataType.Uint32Array, DriverDataType.UInt32)]
    [InlineData(SparkplugDataType.Uint64Array, DriverDataType.UInt64)]
    [InlineData(SparkplugDataType.FloatArray, DriverDataType.Float32)]
    [InlineData(SparkplugDataType.DoubleArray, DriverDataType.Float64)]
    [InlineData(SparkplugDataType.BooleanArray, DriverDataType.Boolean)]
    [InlineData(SparkplugDataType.StringArray, DriverDataType.String)]
    [InlineData(SparkplugDataType.DateTimeArray, DriverDataType.DateTime)]
    public void ToDriverDataType_ArrayVariants_MapToElementType_AndFlagIsArray(SparkplugDataType s, DriverDataType d)
    {
        s.ToDriverDataType().ShouldBe(d);
        s.IsSparkplugArray().ShouldBeTrue();
    }

    [Theory]
    [InlineData(SparkplugDataType.Int32)]
    [InlineData(SparkplugDataType.Boolean)]
    [InlineData(SparkplugDataType.String)]
    [InlineData(SparkplugDataType.Bytes)]
    public void IsSparkplugArray_ScalarTypes_ReturnsFalse(SparkplugDataType s)
        => s.IsSparkplugArray().ShouldBeFalse();

    /// <summary>
    ///     Design §3.5: "DataSet, Template → unsupported v1". Extended here to the two PropertyValue-only
    ///     variants and the proto's explicit placeholder — all five must come back <see langword="null"/>,
    ///     never a guessed <see cref="DriverDataType.String"/>.
    /// </summary>
    [Theory]
    [InlineData(SparkplugDataType.Unknown)]
    [InlineData(SparkplugDataType.DataSet)]
    [InlineData(SparkplugDataType.Template)]
    [InlineData(SparkplugDataType.PropertySet)]
    [InlineData(SparkplugDataType.PropertySetList)]
    public void ToDriverDataType_UnsupportedTypes_ReturnsNull_NotAGuess(SparkplugDataType s)
        => s.ToDriverDataType().ShouldBeNull();

    /// <summary>
    ///     The drift guard (see class remarks). Enumerates the live generated <c>DataType</c> member
    ///     set (via the <c>SparkplugDataType</c> alias) and asserts every member is either mapped to a
    ///     <see cref="DriverDataType"/> or is one of the five documented-unsupported members — nothing
    ///     silently falls through the map's <c>_ =&gt; null</c> default undetected.
    /// </summary>
    [Fact]
    public void ToDriverDataType_HandlesEveryGeneratedDataTypeMember_MappedOrExplicitlyUnsupported()
    {
        var unsupported = new HashSet<SparkplugDataType>
        {
            SparkplugDataType.Unknown,
            SparkplugDataType.DataSet,
            SparkplugDataType.Template,
            SparkplugDataType.PropertySet,
            SparkplugDataType.PropertySetList,
        };

        var allMembers = Enum.GetValues<SparkplugDataType>();
        allMembers.Length.ShouldBe(35, "this pins the generated member count Task 15 vendored; a changed count means the proto changed and every assertion below needs re-auditing.");

        foreach (var value in allMembers)
        {
            var mapped = value.ToDriverDataType();
            if (unsupported.Contains(value))
            {
                mapped.ShouldBeNull($"{value} is documented unsupported (design §3.5) and must map to null, not a guessed DriverDataType.");
            }
            else
            {
                mapped.ShouldNotBeNull($"{value} has no ToDriverDataType() mapping — every generated DataType member must be mapped or explicitly listed as unsupported.");
            }
        }
    }
}
