using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer;

namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests;

/// <summary>
/// M2.4 (#8): the alarm conditionFilter is a comma-separated, case-insensitive
/// list of condition type names. Blank = allow all. These tests pin the
/// parse-once / IsAllowed predicate that the DataConnectionActor uses as the
/// authoritative client-side gate.
/// </summary>
public class AlarmConditionFilterTests
{
    private static NativeAlarmTransition Tx(string typeName,
        AlarmTransitionKind kind = AlarmTransitionKind.Raise) =>
        new("ref", "obj", typeName, kind,
            new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 500),
            "cat", "desc", "msg", "", "", null, DateTimeOffset.UtcNow, "1", "0");

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(",")]
    [InlineData(" , , ")]
    public void NullOrBlankFilter_IsEmpty_AllowsEverything(string? filter)
    {
        var f = AlarmConditionFilter.Parse(filter);
        Assert.True(f.IsEmpty);
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi")));
        Assert.True(f.IsAllowed(Tx("anything-at-all")));
    }

    [Fact]
    public void Parse_SplitsCommaSeparatedList()
    {
        var f = AlarmConditionFilter.Parse("AnalogLimit.Hi,DiscreteAlarm,AnalogLimit.Lo");
        Assert.False(f.IsEmpty);
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi")));
        Assert.True(f.IsAllowed(Tx("DiscreteAlarm")));
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Lo")));
        Assert.False(f.IsAllowed(Tx("AnalogLimit.HiHi")));
    }

    [Fact]
    public void IsAllowed_IsCaseInsensitive()
    {
        var f = AlarmConditionFilter.Parse("AnalogLimit.Hi");
        Assert.True(f.IsAllowed(Tx("analoglimit.hi")));
        Assert.True(f.IsAllowed(Tx("ANALOGLIMIT.HI")));
        Assert.False(f.IsAllowed(Tx("DiscreteAlarm")));
    }

    [Fact]
    public void Parse_TrimsWhitespaceAroundEachName()
    {
        var f = AlarmConditionFilter.Parse("  AnalogLimit.Hi ,\tDiscreteAlarm  ");
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi")));
        Assert.True(f.IsAllowed(Tx("DiscreteAlarm")));
    }

    [Fact]
    public void Parse_DropsEmptyEntries_KeepsNonEmpty()
    {
        var f = AlarmConditionFilter.Parse("AnalogLimit.Hi,,  ,DiscreteAlarm");
        Assert.False(f.IsEmpty);
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi")));
        Assert.True(f.IsAllowed(Tx("DiscreteAlarm")));
        Assert.False(f.IsAllowed(Tx("")));
    }

    [Fact]
    public void IsAllowed_NeverDropsSnapshotCompleteFramingSentinel()
    {
        // SnapshotComplete is a pure framing sentinel (empty AlarmTypeName) that
        // drives the NativeAlarmActor's atomic snapshot swap. A type filter must
        // never swallow it or the snapshot replay never completes.
        var f = AlarmConditionFilter.Parse("AnalogLimit.Hi");
        Assert.True(f.IsAllowed(Tx("", AlarmTransitionKind.SnapshotComplete)));
    }

    [Fact]
    public void IsAllowed_FiltersReplayedSnapshotConditionsByType()
    {
        // Snapshot-kind transitions carry real conditions and ARE filtered.
        var f = AlarmConditionFilter.Parse("AnalogLimit.Hi");
        Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi", AlarmTransitionKind.Snapshot)));
        Assert.False(f.IsAllowed(Tx("DiscreteAlarm", AlarmTransitionKind.Snapshot)));
    }

    [Fact]
    public void Names_ExposesNormalizedSet_ForServerSideOptimization()
    {
        var f = AlarmConditionFilter.Parse(" AnalogLimit.Hi , DiscreteAlarm ");
        Assert.Equal(new[] { "AnalogLimit.Hi", "DiscreteAlarm" }, f.Names.OrderBy(n => n).ToArray());
        Assert.Empty(AlarmConditionFilter.Parse(null).Names);
    }

    [Fact]
    public void IsAllowed_OpcUaResolvedFriendlyName_MatchesFriendlyNameFilter()
    {
        // M2.4 (#8) regression: OPC UA delivers events whose AlarmTypeName, after
        // RealOpcUaClient.ResolveAlarmTypeName, is a standard friendly type name
        // (e.g. "ExclusiveLevelAlarmType"). A friendly-name filter on that source
        // built a correct server WhereClause; the client gate must agree and deliver,
        // not drop every event (which the prior NodeId-string AlarmTypeName caused).
        var f = AlarmConditionFilter.Parse("ExclusiveLevelAlarmType,DiscreteAlarmType");
        Assert.True(f.IsAllowed(Tx("ExclusiveLevelAlarmType")));
        Assert.True(f.IsAllowed(Tx("DiscreteAlarmType")));
        Assert.False(f.IsAllowed(Tx("OffNormalAlarmType")));
    }
}
