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

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

[Trait("Category", "Unit")]
public sealed class OpcUaClientAttributeMappingTests
{
    /// <summary>Verifies that MapUpstreamDataType recognizes standard builtin OPC UA types.</summary>
    /// <param name="typeId">The OPC UA data type ID.</param>
    /// <param name="expected">The expected driver data type.</param>
    [Theory]
    [InlineData((uint)DataTypes.Boolean, DriverDataType.Boolean)]
    [InlineData((uint)DataTypes.SByte, DriverDataType.Int16)]
    [InlineData((uint)DataTypes.Byte, DriverDataType.UInt16)]
    [InlineData((uint)DataTypes.Int16, DriverDataType.Int16)]
    [InlineData((uint)DataTypes.UInt16, DriverDataType.UInt16)]
    [InlineData((uint)DataTypes.Int32, DriverDataType.Int32)]
    [InlineData((uint)DataTypes.UInt32, DriverDataType.UInt32)]
    [InlineData((uint)DataTypes.Int64, DriverDataType.Int64)]
    [InlineData((uint)DataTypes.UInt64, DriverDataType.UInt64)]
    [InlineData((uint)DataTypes.Float, DriverDataType.Float32)]
    [InlineData((uint)DataTypes.Double, DriverDataType.Float64)]
    [InlineData((uint)DataTypes.String, DriverDataType.String)]
    [InlineData((uint)DataTypes.DateTime, DriverDataType.DateTime)]
    public void MapUpstreamDataType_recognizes_standard_builtin_types(uint typeId, DriverDataType expected)
    {
        var nodeId = new NodeId(typeId);
        OpcUaClientDriver.MapUpstreamDataType(nodeId).ShouldBe(expected);
    }

    /// <summary>Verifies that MapUpstreamDataType maps SByte to Int16 since DriverDataType lacks 8-bit signed.</summary>
    [Fact]
    public void MapUpstreamDataType_maps_SByte_to_Int16_since_DriverDataType_lacks_8bit_signed()
    {
        // SByte (signed 8-bit) has no narrower signed type in DriverDataType; widen to Int16.
        OpcUaClientDriver.MapUpstreamDataType(new NodeId((uint)DataTypes.SByte)).ShouldBe(DriverDataType.Int16);
    }

    /// <summary>Verifies that MapUpstreamDataType maps Byte to UInt16 not Int16.</summary>
    [Fact]
    public void MapUpstreamDataType_maps_Byte_to_UInt16_not_Int16()
    {
        // Driver.OpcUaClient-010: Byte is unsigned 0-255 and must map to the unsigned family
        // (UInt16), NOT Int16. The old mapping misrepresented type metadata for downstream
        // clients and range/validation logic keyed off DriverDataType.
        OpcUaClientDriver.MapUpstreamDataType(new NodeId((uint)DataTypes.Byte)).ShouldBe(DriverDataType.UInt16);
    }

    /// <summary>Verifies that MapUpstreamDataType falls back to String for unknown custom types.</summary>
    [Fact]
    public void MapUpstreamDataType_falls_back_to_String_for_unknown_custom_types()
    {
        // Custom vendor extension object — NodeId in namespace 2 that isn't a standard type.
        OpcUaClientDriver.MapUpstreamDataType(new NodeId("CustomStruct", 2)).ShouldBe(DriverDataType.String);
    }

    /// <summary>Verifies that MapUpstreamDataType handles UtcTime as DateTime.</summary>
    [Fact]
    public void MapUpstreamDataType_handles_UtcTime_as_DateTime()
    {
        OpcUaClientDriver.MapUpstreamDataType(new NodeId((uint)DataTypes.UtcTime)).ShouldBe(DriverDataType.DateTime);
    }

    /// <summary>Verifies that MapAccessLevelToSecurityClass respects the CurrentWrite bit.</summary>
    /// <param name="accessLevel">The access level byte.</param>
    /// <param name="expected">The expected security classification.</param>
    [Theory]
    [InlineData((byte)0, SecurityClassification.ViewOnly)]           // no access flags set
    [InlineData((byte)1, SecurityClassification.ViewOnly)]           // CurrentRead only
    [InlineData((byte)2, SecurityClassification.Operate)]            // CurrentWrite only
    [InlineData((byte)3, SecurityClassification.Operate)]            // CurrentRead + CurrentWrite
    [InlineData((byte)0x0F, SecurityClassification.Operate)]          // read+write+historyRead+historyWrite
    [InlineData((byte)0x04, SecurityClassification.ViewOnly)]         // HistoryRead only — no Write bit
    public void MapAccessLevelToSecurityClass_respects_CurrentWrite_bit(byte accessLevel, SecurityClassification expected)
    {
        OpcUaClientDriver.MapAccessLevelToSecurityClass(accessLevel).ShouldBe(expected);
    }
}
