using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Commons.Tests.Browsing;

/// <summary>Shared test doubles for the DiscoveryDriverBrowser suites (Tasks 8–10).</summary>
internal sealed class FakeDiscoveryDriver : IDriver, ITagDiscovery
{
    public int InitializeCalls;
    public int DiscoverCalls;
    public int ShutdownCalls;

    /// <summary>Ordered lifecycle trace ("init"/"discover"/"shutdown").</summary>
    public List<string> Trace { get; } = [];

    /// <summary>Completes once ShutdownAsync has finished (even the fire-and-forget CanBrowse path).</summary>
    public TaskCompletionSource ShutdownDone { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);

    public bool SupportsOnlineDiscovery { get; init; } = true;
    public DiscoveryRediscoverPolicy RediscoverPolicy { get; init; } = DiscoveryRediscoverPolicy.Once;

    /// <summary>Discovery behavior: (builder, 1-based call index) → task. Default streams one leaf.</summary>
    public Func<IAddressSpaceBuilder, int, Task>? OnDiscover { get; init; }
    public Func<CancellationToken, Task>? OnInitialize { get; init; }
    public Func<CancellationToken, Task>? OnShutdown { get; init; }

    public string DriverInstanceId { get; }
    public string DriverType { get; }
    public string? LastConfigJson { get; private set; }

    public FakeDiscoveryDriver(string driverType = "Fake", string id = "fake-1")
    {
        DriverType = driverType;
        DriverInstanceId = id;
    }

    public async Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
    {
        LastConfigJson = driverConfigJson;
        Interlocked.Increment(ref InitializeCalls);
        lock (Trace) Trace.Add("init");
        if (OnInitialize is not null) await OnInitialize(cancellationToken).ConfigureAwait(false);
    }

    public async Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
    {
        var idx = Interlocked.Increment(ref DiscoverCalls);
        lock (Trace) Trace.Add("discover");
        if (OnDiscover is not null)
        {
            await OnDiscover(builder, idx).ConfigureAwait(false);
            return;
        }
        // Default: stream one leaf so the captured tree is non-empty.
        builder.Variable("Tag1", "Tag1",
            new DriverAttributeInfo("Dev.Tag1", DriverDataType.Float64, false, null,
                SecurityClassification.Operate, false));
    }

    public async Task ShutdownAsync(CancellationToken cancellationToken)
    {
        Interlocked.Increment(ref ShutdownCalls);
        lock (Trace) Trace.Add("shutdown");
        try
        {
            if (OnShutdown is not null) await OnShutdown(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            ShutdownDone.TrySetResult();
        }
    }

    public Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask;
    public DriverHealth GetHealth() => new(DriverState.Healthy, null, null);
    public long GetMemoryFootprint() => 0;
    public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

/// <summary>An IDriver that is NOT ITagDiscovery — exercises the not-discovery-capable gate.</summary>
internal sealed class FakeNonDiscoveryDriver(string driverType = "Flat", string id = "flat-1") : IDriver
{
    public int ShutdownCalls;
    public TaskCompletionSource ShutdownDone { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
    public string DriverInstanceId { get; } = id;
    public string DriverType { get; } = driverType;
    public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask;
    public Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken) => Task.CompletedTask;
    public Task ShutdownAsync(CancellationToken cancellationToken)
    {
        Interlocked.Increment(ref ShutdownCalls);
        ShutdownDone.TrySetResult();
        return Task.CompletedTask;
    }
    public DriverHealth GetHealth() => new(DriverState.Healthy, null, null);
    public long GetMemoryFootprint() => 0;
    public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

/// <summary>Configurable IDriverFactory: delegates construction to a supplied func and records what it made.</summary>
internal sealed class FakeDriverFactory(Func<string, string, string, IDriver?> create) : IDriverFactory
{
    public int CreateCalls;
    public List<IDriver> Created { get; } = [];

    public IDriver? TryCreate(string driverType, string driverInstanceId, string driverConfigJson)
    {
        Interlocked.Increment(ref CreateCalls);
        var d = create(driverType, driverInstanceId, driverConfigJson);
        if (d is not null) lock (Created) Created.Add(d);
        return d;
    }

    public IReadOnlyCollection<string> SupportedTypes { get; } = Array.Empty<string>();

    /// <summary>Factory that always returns a fresh default FakeDiscoveryDriver.</summary>
    public static FakeDriverFactory Of(Func<FakeDiscoveryDriver> make) =>
        new((_, _, _) => make());
}
