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

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

[Trait("Category", "Unit")]
public sealed class AbCipDriverTests
{
    /// <summary>Verifies AbCipDriver reports correct driver type and instance ID.</summary>
    [Fact]
    public void DriverType_is_AbCip()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions(), "drv-1");
        drv.DriverType.ShouldBe("AbCip");
        drv.DriverInstanceId.ShouldBe("drv-1");
    }

    /// <summary>Verifies InitializeAsync with no devices succeeds and marks driver healthy.</summary>
    [Fact]
    public async Task InitializeAsync_with_empty_devices_succeeds_and_marks_healthy()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions(), "drv-1");
        await drv.InitializeAsync("{}", CancellationToken.None);
        drv.GetHealth().State.ShouldBe(DriverState.Healthy);
    }

    /// <summary>Verifies InitializeAsync registers devices with their respective PLC family profiles.</summary>
    [Fact]
    public async Task InitializeAsync_registers_each_device_with_its_family_profile()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices =
            [
                new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.ControlLogix),
                new AbCipDeviceOptions("ab://10.0.0.6/", AbCipPlcFamily.Micro800),
            ],
        }, "drv-1");

        await drv.InitializeAsync("{}", CancellationToken.None);

        drv.DeviceCount.ShouldBe(2);
        drv.GetDeviceState("ab://10.0.0.5/1,0")!.Profile.ShouldBe(AbCipPlcFamilyProfile.ControlLogix);
        drv.GetDeviceState("ab://10.0.0.6/")!.Profile.ShouldBe(AbCipPlcFamilyProfile.Micro800);
    }

    /// <summary>Verifies InitializeAsync rejects malformed host addresses and faults the driver.</summary>
    [Fact]
    public async Task InitializeAsync_with_malformed_host_address_faults()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions("not-a-valid-address")],
        }, "drv-1");

        await Should.ThrowAsync<InvalidOperationException>(
            () => drv.InitializeAsync("{}", CancellationToken.None));
        drv.GetHealth().State.ShouldBe(DriverState.Faulted);
    }

    /// <summary>Verifies ShutdownAsync clears devices and marks driver state unknown.</summary>
    [Fact]
    public async Task ShutdownAsync_clears_devices_and_marks_unknown()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
        }, "drv-1");

        await drv.InitializeAsync("{}", CancellationToken.None);
        drv.DeviceCount.ShouldBe(1);

        await drv.ShutdownAsync(CancellationToken.None);
        drv.DeviceCount.ShouldBe(0);
        drv.GetHealth().State.ShouldBe(DriverState.Unknown);
    }

    /// <summary>Verifies ReinitializeAsync stops and restarts all devices.</summary>
    [Fact]
    public async Task ReinitializeAsync_cycles_devices()
    {
        var drv = new AbCipDriver(new AbCipDriverOptions
        {
            Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")],
        }, "drv-1");

        await drv.InitializeAsync("{}", CancellationToken.None);
        await drv.ReinitializeAsync("{}", CancellationToken.None);

        drv.DeviceCount.ShouldBe(1);
        drv.GetHealth().State.ShouldBe(DriverState.Healthy);
    }

    /// <summary>Verifies PLC family profiles expose expected default configuration values.</summary>
    [Fact]
    public void Family_profiles_expose_expected_defaults()
    {
        AbCipPlcFamilyProfile.ControlLogix.LibplctagPlcAttribute.ShouldBe("controllogix");
        AbCipPlcFamilyProfile.ControlLogix.DefaultConnectionSize.ShouldBe(4002);
        AbCipPlcFamilyProfile.ControlLogix.DefaultCipPath.ShouldBe("1,0");

        AbCipPlcFamilyProfile.Micro800.DefaultCipPath.ShouldBe(""); // no backplane routing
        AbCipPlcFamilyProfile.Micro800.SupportsRequestPacking.ShouldBeFalse();
        AbCipPlcFamilyProfile.Micro800.SupportsConnectedMessaging.ShouldBeFalse();

        AbCipPlcFamilyProfile.CompactLogix.DefaultConnectionSize.ShouldBe(504);
        AbCipPlcFamilyProfile.GuardLogix.LibplctagPlcAttribute.ShouldBe("controllogix");
    }

    /// <summary>Verifies AB CIP atomic data types map correctly to driver data types.</summary>
    [Fact]
    public void AbCipDataType_maps_atomics_to_driver_types()
    {
        AbCipDataType.Bool.ToDriverDataType().ShouldBe(DriverDataType.Boolean);
        AbCipDataType.DInt.ToDriverDataType().ShouldBe(DriverDataType.Int32);
        AbCipDataType.Real.ToDriverDataType().ShouldBe(DriverDataType.Float32);
        AbCipDataType.LReal.ToDriverDataType().ShouldBe(DriverDataType.Float64);
        AbCipDataType.String.ToDriverDataType().ShouldBe(DriverDataType.String);
        // Driver.AbCip-004: 64-bit and unsigned-32-bit types must map to their correct equivalents.
        AbCipDataType.LInt.ToDriverDataType().ShouldBe(DriverDataType.Int64);
        AbCipDataType.ULInt.ToDriverDataType().ShouldBe(DriverDataType.UInt64);
        AbCipDataType.UDInt.ToDriverDataType().ShouldBe(DriverDataType.UInt32);
    }
}
