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);
    }

    // ── SelectClause index alignment (M2.13 / #27) ───────────────────────────
    // CRITICAL: HandleAlarmEvent reads fields[N] by position. Verify new clauses
    // are APPENDED at indices 13–18 so existing mappings (0–12) are undisturbed.

    [Fact]
    public void BuildAlarmEventFilter_HasExactly19SelectClauses()
    {
        // Baseline: 6 base fields + 7 A&C sub-state fields + 5 appended fields (13–17)
        // + AckedState/TransitionTime at 18 (MES alarm-status API §6.4) = 19.
        // If this count changes, review HandleAlarmEvent index mappings immediately.
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        Assert.Equal(19, filter.SelectClauses.Count);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index18_IsAcknowledgeableConditionType_AckedState_TransitionTime()
    {
        // MES alarm-status API §6.4: index 18 must be AckedState/TransitionTime → AckTime.
        // APPENDED after the limit fields, so indices 0–17 keep their existing meaning.
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[18];
        Assert.Equal(ObjectTypeIds.AcknowledgeableConditionType, clause.TypeDefinitionId);
        Assert.Equal(2, clause.BrowsePath.Count);
        Assert.Equal("AckedState", clause.BrowsePath[0].Name);
        Assert.Equal("TransitionTime", clause.BrowsePath[1].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index13_IsAlarmConditionType_ActiveState_TransitionTime()
    {
        // Index 13 must be AlarmConditionType/ActiveState/TransitionTime → OriginalRaiseTime.
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[13];
        Assert.Equal(ObjectTypeIds.AlarmConditionType, clause.TypeDefinitionId);
        Assert.Equal(2, clause.BrowsePath.Count);
        Assert.Equal("ActiveState", clause.BrowsePath[0].Name);
        Assert.Equal("TransitionTime", clause.BrowsePath[1].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index14_IsLimitAlarmType_HighHighLimit()
    {
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[14];
        Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
        Assert.Equal("HighHighLimit", clause.BrowsePath[0].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index15_IsLimitAlarmType_HighLimit()
    {
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[15];
        Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
        Assert.Equal("HighLimit", clause.BrowsePath[0].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index16_IsLimitAlarmType_LowLimit()
    {
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[16];
        Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
        Assert.Equal("LowLimit", clause.BrowsePath[0].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_Index17_IsLimitAlarmType_LowLowLimit()
    {
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
        var clause = filter.SelectClauses[17];
        Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
        Assert.Equal("LowLowLimit", clause.BrowsePath[0].Name);
    }

    [Fact]
    public void BuildAlarmEventFilter_ExistingIndices0To12_Unchanged()
    {
        // Guard: the first 13 SelectClauses (indices 0–12) must remain unchanged so
        // that existing HandleAlarmEvent logic is not silently broken by future edits.
        var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);

        // Indices 0–5: base event fields (EventType…Severity) from BaseEventType.
        for (var i = 0; i <= 5; i++)
            Assert.Equal(ObjectTypeIds.BaseEventType, filter.SelectClauses[i].TypeDefinitionId);

        // Index 6: AlarmConditionType/ActiveState/Id
        Assert.Equal(ObjectTypeIds.AlarmConditionType, filter.SelectClauses[6].TypeDefinitionId);
        Assert.Equal("ActiveState", filter.SelectClauses[6].BrowsePath[0].Name);
        Assert.Equal("Id", filter.SelectClauses[6].BrowsePath[1].Name);

        // Index 7: AcknowledgeableConditionType/AckedState/Id
        Assert.Equal(ObjectTypeIds.AcknowledgeableConditionType, filter.SelectClauses[7].TypeDefinitionId);
        Assert.Equal("AckedState", filter.SelectClauses[7].BrowsePath[0].Name);

        // Index 11: ConditionType/ConditionName
        Assert.Equal(ObjectTypeIds.ConditionType, filter.SelectClauses[11].TypeDefinitionId);
        Assert.Equal("ConditionName", filter.SelectClauses[11].BrowsePath[0].Name);

        // Index 12: ConditionType/Comment
        Assert.Equal(ObjectTypeIds.ConditionType, filter.SelectClauses[12].TypeDefinitionId);
        Assert.Equal("Comment", filter.SelectClauses[12].BrowsePath[0].Name);
    }
}
