using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;

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

/// <summary>
///     Tests for the managed helpers inside FwlibNative + FwlibFocasClient that don't require the
///     licensed Fwlib32.dll — letter→ADR_* mapping, FocasDataType→data-type mapping, byte encoding.
///     The actual P/Invoke calls can only run where the DLL is present; field testing covers those.
/// </summary>
[Trait("Category", "Unit")]
public sealed class FwlibNativeHelperTests
{
    [Theory]
    [InlineData("G", 0)]
    [InlineData("F", 1)]
    [InlineData("Y", 2)]
    [InlineData("X", 3)]
    [InlineData("A", 4)]
    [InlineData("R", 5)]
    [InlineData("T", 6)]
    [InlineData("K", 7)]
    [InlineData("C", 8)]
    [InlineData("D", 9)]
    [InlineData("E", 10)]
    [InlineData("g", 0)] // case-insensitive
    public void PmcAddrType_maps_every_valid_letter(string letter, short expected)
    {
        FocasPmcAddrType.FromLetter(letter).ShouldBe(expected);
    }

    [Theory]
    [InlineData("Z")]
    [InlineData("")]
    [InlineData("XX")]
    public void PmcAddrType_rejects_unknown_letters(string letter)
    {
        FocasPmcAddrType.FromLetter(letter).ShouldBeNull();
    }

    [Theory]
    [InlineData(FocasDataType.Bit, 0)]   // byte
    [InlineData(FocasDataType.Byte, 0)]
    [InlineData(FocasDataType.Int16, 1)]  // word
    [InlineData(FocasDataType.Int32, 2)]  // long
    [InlineData(FocasDataType.Float32, 4)]
    [InlineData(FocasDataType.Float64, 5)]
    public void PmcDataType_maps_FocasDataType_to_FOCAS_code(FocasDataType input, short expected)
    {
        FocasPmcDataType.FromFocasDataType(input).ShouldBe(expected);
    }

    [Fact]
    public void EncodePmcValue_Byte_writes_signed_byte_at_offset_0()
    {
        var buf = new byte[40];
        FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Byte, (sbyte)-5, bitIndex: null);
        ((sbyte)buf[0]).ShouldBe((sbyte)-5);
    }

    [Fact]
    public void EncodePmcValue_Int16_writes_little_endian()
    {
        var buf = new byte[40];
        FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Int16, (short)0x1234, bitIndex: null);
        buf[0].ShouldBe((byte)0x34);
        buf[1].ShouldBe((byte)0x12);
    }

    [Fact]
    public void EncodePmcValue_Int32_writes_little_endian()
    {
        var buf = new byte[40];
        FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Int32, 0x12345678, bitIndex: null);
        buf[0].ShouldBe((byte)0x78);
        buf[1].ShouldBe((byte)0x56);
        buf[2].ShouldBe((byte)0x34);
        buf[3].ShouldBe((byte)0x12);
    }

    [Fact]
    public void EncodePmcValue_Bit_without_bit_index_writes_byte_boolean()
    {
        // Task #181 closed the Bit-write gap — PMC Bit with a bitIndex now routes through
        // WritePmcBitAsync's RMW path upstream, and raw EncodePmcValue only gets the
        // no-bit-index case (treated as a whole-byte boolean).
        var buf = new byte[40];
        FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Bit, true, bitIndex: null);
        buf[0].ShouldBe((byte)1);

        FwlibFocasClient.EncodePmcValue(buf, FocasDataType.Bit, false, bitIndex: null);
        buf[0].ShouldBe((byte)0);
    }

    [Fact]
    public void EncodeParamValue_Int32_writes_little_endian()
    {
        var buf = new byte[32];
        FwlibFocasClient.EncodeParamValue(buf, FocasDataType.Int32, 0x0A0B0C0D);
        buf[0].ShouldBe((byte)0x0D);
        buf[1].ShouldBe((byte)0x0C);
        buf[2].ShouldBe((byte)0x0B);
        buf[3].ShouldBe((byte)0x0A);
    }
}
