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

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

/// <summary>
///     Covers the Phase 7 driver-to-engine bridge cache (task #243). Verifies the
///     cache serves last-known values synchronously, fans out Push updates to
///     subscribers, and cleans up on Dispose.
/// </summary>
[Trait("Category", "Unit")]
public sealed class CachedTagUpstreamSourceTests
{
    private static DataValueSnapshot Snap(object? v) =>
        new(v, 0u, DateTime.UtcNow, DateTime.UtcNow);

    [Fact]
    public void ReadTag_unknown_path_returns_BadNodeIdUnknown_snapshot()
    {
        var c = new CachedTagUpstreamSource();
        var snap = c.ReadTag("/nowhere");
        snap.Value.ShouldBeNull();
        snap.StatusCode.ShouldBe(CachedTagUpstreamSource.UpstreamNotConfigured);
    }

    [Fact]
    public void Push_then_Read_returns_cached_value()
    {
        var c = new CachedTagUpstreamSource();
        c.Push("/Line1/Temp", Snap(42));
        c.ReadTag("/Line1/Temp").Value.ShouldBe(42);
    }

    [Fact]
    public void Push_fans_out_to_subscribers_in_registration_order()
    {
        var c = new CachedTagUpstreamSource();
        var events = new List<string>();
        c.SubscribeTag("/X", (p, s) => events.Add($"A:{p}:{s.Value}"));
        c.SubscribeTag("/X", (p, s) => events.Add($"B:{p}:{s.Value}"));

        c.Push("/X", Snap(7));

        events.ShouldBe(["A:/X:7", "B:/X:7"]);
    }

    [Fact]
    public void Push_to_different_path_does_not_fire_foreign_observer()
    {
        var c = new CachedTagUpstreamSource();
        var fired = 0;
        c.SubscribeTag("/X", (_, _) => fired++);

        c.Push("/Y", Snap(1));
        fired.ShouldBe(0);
    }

    [Fact]
    public void Dispose_of_subscription_stops_fan_out()
    {
        var c = new CachedTagUpstreamSource();
        var fired = 0;
        var sub = c.SubscribeTag("/X", (_, _) => fired++);

        c.Push("/X", Snap(1));
        sub.Dispose();
        c.Push("/X", Snap(2));

        fired.ShouldBe(1);
    }

    [Fact]
    public void Satisfies_both_VirtualTag_and_ScriptedAlarm_upstream_interfaces()
    {
        var c = new CachedTagUpstreamSource();
        // Single instance is assignable to both — the composer passes it through for
        // both engine constructors per the task #243 wiring.
        ((Core.VirtualTags.ITagUpstreamSource)c).ShouldNotBeNull();
        ((Core.ScriptedAlarms.ITagUpstreamSource)c).ShouldNotBeNull();
    }
}
