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

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

/// <summary>
///     Scaffold tests for <see cref="OpcUaClientDriver"/>'s <see cref="ITagDiscovery"/>
///     surface that don't require a live remote server. Live-browse coverage lands in a
///     follow-up PR once the in-process OPC UA server fixture is scaffolded.
/// </summary>
[Trait("Category", "Unit")]
public sealed class OpcUaClientDiscoveryTests
{
    [Fact]
    public async Task DiscoverAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-disco");
        var builder = new NullAddressSpaceBuilder();
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken));
    }

    [Fact]
    public void DiscoverAsync_rejects_null_builder()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-disco");
        Should.ThrowAsync<ArgumentNullException>(async () =>
            await drv.DiscoverAsync(null!, TestContext.Current.CancellationToken));
    }

    [Fact]
    public void Discovery_caps_are_sensible_defaults()
    {
        var opts = new OpcUaClientDriverOptions();
        opts.MaxDiscoveredNodes.ShouldBe(10_000, "bounds memory on runaway servers without clipping normal models");
        opts.MaxBrowseDepth.ShouldBe(10, "deep enough for realistic info models; shallow enough for cycle safety");
        opts.BrowseRoot.ShouldBeNull("null = default to ObjectsFolder i=85");
    }

    private sealed class NullAddressSpaceBuilder : IAddressSpaceBuilder
    {
        public IAddressSpaceBuilder Folder(string browseName, string displayName) => this;
        public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
            => new StubHandle();
        public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
        public void AttachAlarmCondition(IVariableHandle sourceVariable, string alarmName, DriverAttributeInfo alarmInfo) { }

        private sealed class StubHandle : IVariableHandle
        {
            public string FullReference => "stub";
            public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => throw new NotSupportedException();
        }
    }
}
