using Shouldly;
using Xunit;

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

[Trait("Category", "Unit")]
public sealed class FocasHandleRecycleTests
{
    /// <summary>Verifies that the recycle loop disposes clients on interval and reopens fresh ones.</summary>
    [Fact]
    public async Task Recycle_loop_disposes_client_on_interval_reads_reopen_fresh_one()
    {
        var factory = new FakeFocasClientFactory();
        var drv = new FocasDriver(new FocasDriverOptions
        {
            Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")],
            RawTags = [FocasRawTags.Entry(new FocasTagDefinition("R", "focas://10.0.0.5:8193", "R100", FocasDataType.Byte))],
            Probe = new FocasProbeOptions { Enabled = false },
            HandleRecycle = new FocasHandleRecycleOptions
            {
                Enabled = true,
                Interval = TimeSpan.FromMilliseconds(80),
            },
        }, "drv-1", factory);

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

        // First read forces the initial connect.
        await drv.ReadAsync(["R"], CancellationToken.None);
        var initialClients = factory.Clients.Count;
        initialClients.ShouldBe(1);

        // Wait for a recycle tick, then read again — a new client must have been created.
        await WaitFor(() => factory.Clients[0].DisposeCount > 0, TimeSpan.FromSeconds(3));
        await drv.ReadAsync(["R"], CancellationToken.None);

        factory.Clients.Count.ShouldBeGreaterThan(initialClients);

        await drv.ShutdownAsync(CancellationToken.None);
    }

    /// <summary>Verifies that the recycle loop stays off when not enabled.</summary>
    [Fact]
    public async Task Recycle_loop_stays_off_when_not_enabled()
    {
        var factory = new FakeFocasClientFactory();
        var drv = new FocasDriver(new FocasDriverOptions
        {
            Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")],
            RawTags = [FocasRawTags.Entry(new FocasTagDefinition("R", "focas://10.0.0.5:8193", "R100", FocasDataType.Byte))],
            Probe = new FocasProbeOptions { Enabled = false },
        }, "drv-1", factory);

        await drv.InitializeAsync("{}", CancellationToken.None);
        await drv.ReadAsync(["R"], CancellationToken.None);
        await Task.Delay(150);

        // With recycle off the same client stays live — no Dispose during the window.
        factory.Clients.Count.ShouldBe(1);
        factory.Clients[0].DisposeCount.ShouldBe(0);

        await drv.ShutdownAsync(CancellationToken.None);
    }

    private static async Task WaitFor(Func<bool> pred, TimeSpan timeout)
    {
        var deadline = DateTime.UtcNow + timeout;
        while (DateTime.UtcNow < deadline)
        {
            if (pred()) return;
            await Task.Delay(20);
        }
    }
}
