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

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

[Trait("Category", "Unit")]
public sealed class AbLegacyBitRmwTests
{
    /// <summary>Verifies that setting a bit reads the parent word, ORs the bit, and writes back.</summary>
    [Fact]
    public async Task Bit_set_reads_parent_word_ORs_bit_writes_back()
    {
        var factory = new FakeAbLegacyTagFactory
        {
            Customise = p => new FakeAbLegacyTag(p) { Value = (short)0b0001 },
        };
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
            Tags = [new AbLegacyTagDefinition("Flag3", "ab://10.0.0.5/1,0", "N7:0/3", AbLegacyDataType.Bit)],
            Probe = new AbLegacyProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        var results = await drv.WriteAsync(
            [new WriteRequest("Flag3", true)], CancellationToken.None);

        results.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good);
        factory.Tags.ShouldContainKey("N7:0"); // parent word runtime created
        Convert.ToInt32(factory.Tags["N7:0"].Value).ShouldBe(0b1001);
    }

    /// <summary>Verifies that clearing a bit preserves other bits in the word.</summary>
    [Fact]
    public async Task Bit_clear_preserves_other_bits_in_N_file_word()
    {
        var factory = new FakeAbLegacyTagFactory
        {
            Customise = p => new FakeAbLegacyTag(p) { Value = unchecked((short)0xFFFF) },
        };
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
            Tags = [new AbLegacyTagDefinition("F", "ab://10.0.0.5/1,0", "N7:0/3", AbLegacyDataType.Bit)],
            Probe = new AbLegacyProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.WriteAsync([new WriteRequest("F", false)], CancellationToken.None);

        Convert.ToInt32(factory.Tags["N7:0"].Value).ShouldBe(unchecked((short)0xFFF7));
    }

    /// <summary>Verifies that concurrent bit writes to the same word compose correctly.</summary>
    [Fact]
    public async Task Concurrent_bit_writes_to_same_word_compose_correctly()
    {
        var factory = new FakeAbLegacyTagFactory
        {
            Customise = p => new FakeAbLegacyTag(p) { Value = (short)0 },
        };
        var tags = Enumerable.Range(0, 8)
            .Select(b => new AbLegacyTagDefinition($"Bit{b}", "ab://10.0.0.5/1,0", $"N7:0/{b}", AbLegacyDataType.Bit))
            .ToArray();
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
            Tags = tags,
            Probe = new AbLegacyProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await Task.WhenAll(Enumerable.Range(0, 8).Select(b =>
            drv.WriteAsync([new WriteRequest($"Bit{b}", true)], CancellationToken.None)));

        Convert.ToInt32(factory.Tags["N7:0"].Value).ShouldBe(0xFF);
    }

    /// <summary>Verifies that repeated bit writes reuse the parent word runtime.</summary>
    [Fact]
    public async Task Repeat_bit_writes_reuse_parent_runtime()
    {
        var factory = new FakeAbLegacyTagFactory
        {
            Customise = p => new FakeAbLegacyTag(p) { Value = (short)0 },
        };
        var drv = new AbLegacyDriver(new AbLegacyDriverOptions
        {
            Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
            Tags =
            [
                new AbLegacyTagDefinition("Bit0", "ab://10.0.0.5/1,0", "N7:0/0", AbLegacyDataType.Bit),
                new AbLegacyTagDefinition("Bit5", "ab://10.0.0.5/1,0", "N7:0/5", AbLegacyDataType.Bit),
            ],
            Probe = new AbLegacyProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.WriteAsync([new WriteRequest("Bit0", true)], CancellationToken.None);
        await drv.WriteAsync([new WriteRequest("Bit5", true)], CancellationToken.None);

        factory.Tags["N7:0"].InitializeCount.ShouldBe(1);
        factory.Tags["N7:0"].WriteCount.ShouldBe(2);
        Convert.ToInt32(factory.Tags["N7:0"].Value).ShouldBe(0x21); // bits 0 + 5
    }
}
