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

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

public sealed class LastValueCacheTests
{
    // OPC UA BadWaitingForInitialData (0x80320000) — the repo-wide convention for "no value
    // observed yet" (mirrored by CalculationDriver, VirtualTagEngine, FOCAS, and
    // OtOpcUaNodeManager for freshly-materialised / not-yet-evaluated nodes). MQTT is
    // subscribe-first, so an unseen RawPath is in exactly that state until its first publish.
    private const uint BadWaitingForInitialData = 0x80320000u;

    [Fact]
    public void Read_UnseenRef_ReturnsPerRefWaitingForInitialData_DoesNotThrow()
    {
        var cache = new LastValueCache();

        var snap = cache.Read("factory/oven/temp");

        snap.StatusCode.ShouldBe(BadWaitingForInitialData);
        snap.Value.ShouldBeNull();
    }

    [Fact]
    public void Update_ThenRead_ReturnsLastValue()
    {
        var cache = new LastValueCache();
        var now = DateTime.UtcNow;

        cache.Update("factory/oven/temp", new DataValueSnapshot(42.0, 0u, now, now));

        var snap = cache.Read("factory/oven/temp");
        snap.Value.ShouldBe(42.0);
        snap.StatusCode.ShouldBe(0u);
    }

    [Fact]
    public void Update_OverwritesPreviousValue_ForSameRawPath()
    {
        var cache = new LastValueCache();
        var t1 = DateTime.UtcNow;
        var t2 = t1.AddSeconds(1);

        cache.Update("factory/oven/temp", new DataValueSnapshot(1.0, 0u, t1, t1));
        cache.Update("factory/oven/temp", new DataValueSnapshot(2.0, 0u, t2, t2));

        cache.Read("factory/oven/temp").Value.ShouldBe(2.0);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    public void Read_NullOrEmptyRawPath_DoesNotThrow_ReturnsWaitingForInitialData(string? rawPath)
    {
        var cache = new LastValueCache();

        var snap = cache.Read(rawPath!);

        snap.StatusCode.ShouldBe(BadWaitingForInitialData);
    }

    [Fact]
    public void Update_NullRawPath_DoesNotThrow()
    {
        var cache = new LastValueCache();

        Should.NotThrow(() => cache.Update(null!, new DataValueSnapshot(1.0, 0u, DateTime.UtcNow, DateTime.UtcNow)));
    }

    [Fact]
    public void Read_DistinguishesUnseenFromGenuinelyBadObservedValue()
    {
        var cache = new LastValueCache();
        var now = DateTime.UtcNow;
        const uint badDeviceFailure = 0x808B0000u;

        cache.Update("factory/oven/temp", new DataValueSnapshot(null, badDeviceFailure, null, now));

        var observedBad = cache.Read("factory/oven/temp");
        var neverObserved = cache.Read("factory/oven/other");

        observedBad.StatusCode.ShouldBe(badDeviceFailure);
        neverObserved.StatusCode.ShouldBe(BadWaitingForInitialData);
        observedBad.StatusCode.ShouldNotBe(neverObserved.StatusCode);
    }
}
