using Opc.Ua;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;

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

/// <summary>
/// M2.4 (#8): the OPC UA EventFilter gains a server-side <see cref="ContentFilter"/>
/// WhereClause as a bandwidth optimisation when a condition-type filter is present.
/// The client-side gate in DataConnectionActor remains authoritative; these tests
/// only pin the filter-shaping. No live server required — pure SDK object building.
/// </summary>
public class RealOpcUaClientAlarmFilterTests
{
    [Fact]
    public void BuildAlarmEventFilter_NoFilter_HasNoWhereClause()
    {
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        Assert.NotEmpty(filter.SelectClauses);
        Assert.Empty(filter.WhereClause.Elements);
    }

    [Fact]
    public void BuildAlarmEventFilter_WithKnownTypes_BuildsNonEmptyWhereClause()
    {
        var parsed = AlarmConditionFilter.Parse("LimitAlarmType,DiscreteAlarmType");
        var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);

        Assert.NotEmpty(filter.WhereClause.Elements);
        // Two known types → two OfType operands (OR'd when more than one).
        var ofTypeCount = filter.WhereClause.Elements.Count(e => e.FilterOperator == FilterOperator.OfType);
        Assert.Equal(2, ofTypeCount);
        Assert.Contains(filter.WhereClause.Elements, e => e.FilterOperator == FilterOperator.Or);
    }

    [Fact]
    public void BuildAlarmEventFilter_SingleKnownType_BuildsSingleOfType_NoOr()
    {
        var parsed = AlarmConditionFilter.Parse("AlarmConditionType");
        var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);

        Assert.Single(filter.WhereClause.Elements);
        Assert.Equal(FilterOperator.OfType, filter.WhereClause.Elements[0].FilterOperator);
    }

    [Fact]
    public void BuildAlarmEventFilter_TypeMatchingIsCaseInsensitive()
    {
        var parsed = AlarmConditionFilter.Parse("limitalarmtype");
        var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);
        Assert.Single(filter.WhereClause.Elements, e => e.FilterOperator == FilterOperator.OfType);
    }

    [Fact]
    public void BuildAlarmEventFilter_AllUnknownTypes_OmitsWhereClause()
    {
        // Custom/vendor type names we cannot map to standard NodeIds are skipped
        // server-side; the client-side gate still enforces them. Omitting the
        // WhereClause is the safe choice — a partial WhereClause would drop the
        // unmapped types at the server and break correctness.
        var parsed = AlarmConditionFilter.Parse("MyVendorCustomAlarm,AnotherCustomThing");
        var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);
        Assert.Empty(filter.WhereClause.Elements);
    }

    [Fact]
    public void BuildAlarmEventFilter_MixedKnownAndUnknown_OmitsWhereClause()
    {
        // If ANY requested type can't be mapped, a server-side WhereClause would
        // silently drop that type's events — so we omit the optimisation entirely
        // and let the (authoritative) client gate do the filtering.
        var parsed = AlarmConditionFilter.Parse("LimitAlarmType,MyVendorCustomAlarm");
        var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);
        Assert.Empty(filter.WhereClause.Elements);
    }
}
