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

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

[Trait("Category", "Unit")]
public sealed class TwinCATDriverTests
{
    /// <summary>Verifies that the driver type is correctly identified as TwinCAT.</summary>
    [Fact]
    public void DriverType_is_TwinCAT()
    {
        var drv = new TwinCATDriver(new TwinCATDriverOptions(), "drv-1");
        drv.DriverType.ShouldBe("TwinCAT");
        drv.DriverInstanceId.ShouldBe("drv-1");
    }

    /// <summary>Verifies that device addresses are parsed during initialization.</summary>
    [Fact]
    public async Task InitializeAsync_parses_device_addresses()
    {
        var drv = new TwinCATDriver(new TwinCATDriverOptions
        {
            Devices =
            [
                new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851"),
                new TwinCATDeviceOptions("ads://10.0.0.1.1.1:852", DeviceName: "Machine2"),
            ],
        }, "drv-1");

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

        drv.DeviceCount.ShouldBe(2);
        drv.GetDeviceState("ads://5.23.91.23.1.1:851")!.ParsedAddress.Port.ShouldBe(851);
        drv.GetDeviceState("ads://10.0.0.1.1.1:852")!.Options.DeviceName.ShouldBe("Machine2");
    }

    /// <summary>Verifies that malformed device addresses cause initialization to fault.</summary>
    [Fact]
    public async Task InitializeAsync_malformed_address_faults()
    {
        var drv = new TwinCATDriver(new TwinCATDriverOptions
        {
            Devices = [new TwinCATDeviceOptions("not-an-address")],
        }, "drv-1");

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

    /// <summary>Verifies that shutdown clears all devices.</summary>
    [Fact]
    public async Task ShutdownAsync_clears_devices()
    {
        var drv = new TwinCATDriver(new TwinCATDriverOptions
        {
            Devices = [new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851")],
            Probe = new TwinCATProbeOptions { Enabled = false },
        }, "drv-1");
        await drv.InitializeAsync("{}", CancellationToken.None);

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

    /// <summary>Verifies that reinitialization cycles devices.</summary>
    [Fact]
    public async Task ReinitializeAsync_cycles_devices()
    {
        var drv = new TwinCATDriver(new TwinCATDriverOptions
        {
            Devices = [new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851")],
            Probe = new TwinCATProbeOptions { Enabled = false },
        }, "drv-1");
        await drv.InitializeAsync("{}", CancellationToken.None);
        await drv.ReinitializeAsync("{}", CancellationToken.None);

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

    /// <summary>Verifies that data type mapping covers atomic IEC types.</summary>
    [Fact]
    public void DataType_mapping_covers_atomic_iec_types()
    {
        TwinCATDataType.Bool.ToDriverDataType().ShouldBe(DriverDataType.Boolean);
        TwinCATDataType.DInt.ToDriverDataType().ShouldBe(DriverDataType.Int32);
        TwinCATDataType.Real.ToDriverDataType().ShouldBe(DriverDataType.Float32);
        TwinCATDataType.LReal.ToDriverDataType().ShouldBe(DriverDataType.Float64);
        TwinCATDataType.String.ToDriverDataType().ShouldBe(DriverDataType.String);
        TwinCATDataType.WString.ToDriverDataType().ShouldBe(DriverDataType.String);
        // IEC TIME/DATE/DT/TOD surface as their raw UDINT counter (Driver.TwinCAT-002).
        TwinCATDataType.Time.ToDriverDataType().ShouldBe(DriverDataType.UInt32);
    }

    /// <summary>Verifies that status mapper covers known ADS error codes.</summary>
    /// <param name="adsError">The raw ADS error code to map.</param>
    /// <param name="expected">The expected OPC UA status code after mapping.</param>
    [Theory]
    [InlineData(0u,     TwinCATStatusMapper.Good)]
    // Device-layer codes — confirmed from Beckhoff.TwinCAT.Ads 7.0.172 AdsErrorCode enum
    [InlineData(1792u,  TwinCATStatusMapper.BadDeviceFailure)]      // DeviceError (generic)
    [InlineData(1793u,  TwinCATStatusMapper.BadNotSupported)]       // DeviceServiceNotSupported
    [InlineData(1794u,  TwinCATStatusMapper.BadOutOfRange)]         // DeviceInvalidGroup (ADS index-group error)
    [InlineData(1798u,  TwinCATStatusMapper.BadTypeMismatch)]       // DeviceInvalidData (data format mismatch)
    [InlineData(1804u,  TwinCATStatusMapper.BadNodeIdUnknown)]      // DeviceNotFound
    [InlineData(1808u,  TwinCATStatusMapper.BadNodeIdUnknown)]      // DeviceSymbolNotFound
    [InlineData(1809u,  TwinCATStatusMapper.BadInvalidState)]       // DeviceSymbolVersionInvalid — rediscovery trigger
    [InlineData(1827u,  TwinCATStatusMapper.BadNotWritable)]        // DeviceAccessDenied
    [InlineData(1861u,  TwinCATStatusMapper.BadTimeout)]            // ClientSyncTimeOut
    // AMS router errors
    [InlineData(7u,     TwinCATStatusMapper.BadCommunicationError)] // TargetMachineNotFound
    [InlineData(99999u, TwinCATStatusMapper.BadCommunicationError)] // unknown → generic comm fail
    public void StatusMapper_covers_known_ads_error_codes(uint adsError, uint expected)
    {
        TwinCATStatusMapper.MapAdsError(adsError).ShouldBe(expected);
    }
}
