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

namespace ZB.MOM.WW.OtOpcUa.Server.Tests;

/// <summary>
///     Phase 7 Stream G follow-up — verifies the NodeSourceKind dispatch kernel that
///     DriverNodeManager's OnReadValue + OnWriteValue use to route per-node calls to
///     the right backend per ADR-002. Pure functions; no OPC UA stack required.
/// </summary>
[Trait("Category", "Unit")]
public sealed class DriverNodeManagerSourceDispatchTests
{
    private sealed class FakeReadable : IReadable
    {
        public string Name { get; init; } = "";
        public Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
            IReadOnlyList<string> fullReferences, CancellationToken cancellationToken) =>
            Task.FromResult<IReadOnlyList<DataValueSnapshot>>([]);
    }

    [Fact]
    public void Driver_source_routes_to_driver_readable()
    {
        var drv = new FakeReadable { Name = "drv" };
        var vt = new FakeReadable { Name = "vt" };
        var al = new FakeReadable { Name = "al" };

        DriverNodeManager.SelectReadable(NodeSourceKind.Driver, drv, vt, al).ShouldBeSameAs(drv);
    }

    [Fact]
    public void Virtual_source_routes_to_virtual_readable()
    {
        var drv = new FakeReadable();
        var vt = new FakeReadable();
        var al = new FakeReadable();

        DriverNodeManager.SelectReadable(NodeSourceKind.Virtual, drv, vt, al).ShouldBeSameAs(vt);
    }

    [Fact]
    public void ScriptedAlarm_source_routes_to_alarm_readable()
    {
        var drv = new FakeReadable();
        var vt = new FakeReadable();
        var al = new FakeReadable();

        DriverNodeManager.SelectReadable(NodeSourceKind.ScriptedAlarm, drv, vt, al).ShouldBeSameAs(al);
    }

    [Fact]
    public void Virtual_source_without_virtual_readable_returns_null()
    {
        // Engine not wired → dispatch layer surfaces BadNotFound (the null propagates
        // through to the OnReadValue null-check).
        DriverNodeManager.SelectReadable(
            NodeSourceKind.Virtual, driverReadable: new FakeReadable(),
            virtualReadable: null, scriptedAlarmReadable: null).ShouldBeNull();
    }

    [Fact]
    public void ScriptedAlarm_source_without_alarm_readable_returns_null()
    {
        DriverNodeManager.SelectReadable(
            NodeSourceKind.ScriptedAlarm, driverReadable: new FakeReadable(),
            virtualReadable: new FakeReadable(), scriptedAlarmReadable: null).ShouldBeNull();
    }

    [Fact]
    public void Driver_source_without_driver_readable_returns_null()
    {
        // Pre-existing BadNotReadable behavior — unchanged by Phase 7 wiring.
        DriverNodeManager.SelectReadable(
            NodeSourceKind.Driver, driverReadable: null,
            virtualReadable: new FakeReadable(), scriptedAlarmReadable: new FakeReadable()).ShouldBeNull();
    }

    [Fact]
    public void IsWriteAllowedBySource_only_Driver_returns_true()
    {
        // Plan decision #6 — OPC UA writes to virtual tags / scripted alarms rejected.
        DriverNodeManager.IsWriteAllowedBySource(NodeSourceKind.Driver).ShouldBeTrue();
        DriverNodeManager.IsWriteAllowedBySource(NodeSourceKind.Virtual).ShouldBeFalse();
        DriverNodeManager.IsWriteAllowedBySource(NodeSourceKind.ScriptedAlarm).ShouldBeFalse();
    }
}
