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

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

/// <summary>
///     Regression coverage for Driver.AbCip-013 — driver-specs.md §3 lists per-device
///     <c>AllowPacking</c> and <c>ConnectionSize</c> as configurable connection settings, but
///     the implementation previously hard-coded both from the family profile and never wired
///     them through to the libplctag <c>Tag</c>. The fix exposes them as per-device options on
///     <see cref="AbCipDeviceOptions"/>, plumbs them through <see cref="AbCipTagCreateParams"/>,
///     and applies <c>AllowPacking</c> to the live <c>Tag</c>. <c>ConnectionSize</c> is captured
///     for forward-compat (libplctag 1.5.2 does not expose a direct property; the value is
///     plumbed through the create-params so future wrappers / a custom tag-attribute path can
///     consume it without a config-shape change).
/// </summary>
[Trait("Category", "Unit")]
public sealed class AbCipPerDeviceConnectionOptionsTests
{
    private const string Device = "ab://10.0.0.5/1,0";

    /// <summary>Verifies that per-device AllowPacking override is forwarded to tag creation parameters.</summary>
    [Fact]
    public async Task Device_AllowPacking_override_is_forwarded_to_tag_create_params()
    {
        // Driver.AbCip-013 — operator opts out of request packing on a specific device.
        var factory = new FakeAbCipTagFactory();
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions(Device, AllowPacking: false)],
            RawTags = AbCipRawTags.From(new AbCipTagDefinition("Speed", Device, "Speed", AbCipDataType.DInt)),
            Probe = new AbCipProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.ReadAsync(["Speed"], CancellationToken.None);

        factory.Tags["Speed"].CreationParams.AllowPacking.ShouldBeFalse();
    }

    /// <summary>Verifies that AllowPacking defaults inherit from the family profile when not overridden.</summary>
    [Fact]
    public async Task Device_AllowPacking_default_inherits_from_family_profile()
    {
        // No per-device override — default falls back to the family profile's value (true for
        // ControlLogix, false for Micro800).
        var factory = new FakeAbCipTagFactory();
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions(Device)],
            RawTags = AbCipRawTags.From(new AbCipTagDefinition("Speed", Device, "Speed", AbCipDataType.DInt)),
            Probe = new AbCipProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.ReadAsync(["Speed"], CancellationToken.None);

        // ControlLogix profile has SupportsRequestPacking = true.
        factory.Tags["Speed"].CreationParams.AllowPacking.ShouldBeTrue();
    }

    /// <summary>Verifies that Micro800 devices have AllowPacking defaulting to false from the family profile.</summary>
    [Fact]
    public async Task Micro800_default_AllowPacking_is_false_from_family_profile()
    {
        const string micro = "ab://10.0.0.6/";
        var factory = new FakeAbCipTagFactory();
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions(micro, AbCipPlcFamily.Micro800)],
            RawTags = AbCipRawTags.From(new AbCipTagDefinition("X", micro, "X", AbCipDataType.DInt)),
            Probe = new AbCipProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.ReadAsync(["X"], CancellationToken.None);

        // Micro800 profile defaults SupportsRequestPacking = false.
        factory.Tags["X"].CreationParams.AllowPacking.ShouldBeFalse();
    }

    /// <summary>Verifies that per-device ConnectionSize override is forwarded to tag creation parameters.</summary>
    [Fact]
    public async Task Device_ConnectionSize_override_is_forwarded_to_tag_create_params()
    {
        var factory = new FakeAbCipTagFactory();
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions(Device, ConnectionSize: 504)],
            RawTags = AbCipRawTags.From(new AbCipTagDefinition("Speed", Device, "Speed", AbCipDataType.DInt)),
            Probe = new AbCipProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.ReadAsync(["Speed"], CancellationToken.None);

        factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(504);
    }

    /// <summary>Verifies that ConnectionSize defaults inherit from the family profile when not overridden.</summary>
    [Fact]
    public async Task Device_ConnectionSize_default_inherits_from_family_profile()
    {
        var factory = new FakeAbCipTagFactory();
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions(Device)],
            RawTags = AbCipRawTags.From(new AbCipTagDefinition("Speed", Device, "Speed", AbCipDataType.DInt)),
            Probe = new AbCipProbeOptions { Enabled = false },
        }, "drv-1", factory);
        await drv.InitializeAsync("{}", CancellationToken.None);

        await drv.ReadAsync(["Speed"], CancellationToken.None);

        // ControlLogix family default ConnectionSize is 4002 (Large Forward Open).
        factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(4002);
    }

    /// <summary>Verifies that AllowPacking and ConnectionSize round-trip correctly through JSON parsing.</summary>
    [Fact]
    public void AbCipDriverFactoryExtensions_ParseOptions_round_trips_AllowPacking_and_ConnectionSize()
    {
        const string json = """
            {
              "Devices": [ {
                "HostAddress": "ab://10.0.0.5/1,0",
                "PlcFamily": "ControlLogix",
                "AllowPacking": false,
                "ConnectionSize": 504
              } ]
            }
            """;

        var opts = AbCipDriverFactoryExtensions.ParseOptions("drv-1", json);

        opts.Devices.Single().AllowPacking.ShouldBe(false);
        opts.Devices.Single().ConnectionSize.ShouldBe(504);
    }
}
