using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Flattening;

public class DiffServiceTests
{
    private readonly DiffService _sut = new();

    [Fact]
    public void ComputeDiff_NullOldConfig_AllAdded()
    {
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" },
                new ResolvedAttribute { CanonicalName = "Status", Value = "OK", DataType = "String" }
            ],
            Alarms =
            [
                new ResolvedAlarm { CanonicalName = "HighTemp", TriggerType = "RangeViolation" }
            ],
            Scripts =
            [
                new ResolvedScript { CanonicalName = "Monitor", Code = "// code" }
            ]
        };

        var diff = _sut.ComputeDiff(null, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Equal(2, diff.AttributeChanges.Count);
        Assert.All(diff.AttributeChanges, c => Assert.Equal(DiffChangeType.Added, c.ChangeType));
        Assert.Single(diff.AlarmChanges);
        Assert.Single(diff.ScriptChanges);
    }

    [Fact]
    public void ComputeDiff_IdenticalConfigs_NoChanges()
    {
        var config = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = [new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }],
            Alarms = [],
            Scripts = []
        };

        var diff = _sut.ComputeDiff(config, config);

        Assert.False(diff.HasChanges);
        Assert.Empty(diff.AttributeChanges);
    }

    [Fact]
    public void ComputeDiff_AttributeRemoved_DetectedAsRemoved()
    {
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" },
                new ResolvedAttribute { CanonicalName = "Removed", Value = "x", DataType = "String" }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = [new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.AttributeChanges);
        Assert.Equal(DiffChangeType.Removed, diff.AttributeChanges[0].ChangeType);
        Assert.Equal("Removed", diff.AttributeChanges[0].CanonicalName);
    }

    [Fact]
    public void ComputeDiff_AttributeChanged_DetectedAsChanged()
    {
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = [new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = [new ResolvedAttribute { CanonicalName = "Temp", Value = "50", DataType = "Double" }]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.AttributeChanges);
        Assert.Equal(DiffChangeType.Changed, diff.AttributeChanges[0].ChangeType);
        Assert.Equal("25", diff.AttributeChanges[0].OldValue?.Value);
        Assert.Equal("50", diff.AttributeChanges[0].NewValue?.Value);
    }

    [Fact]
    public void ComputeDiff_RevisionHashes_Included()
    {
        var config = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };
        var diff = _sut.ComputeDiff(config, config, "sha256:old", "sha256:new");

        Assert.Equal("sha256:old", diff.OldRevisionHash);
        Assert.Equal("sha256:new", diff.NewRevisionHash);
    }

    [Fact]
    public void ComputeDiff_ScriptCodeChange_Detected()
    {
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Scripts = [new ResolvedScript { CanonicalName = "Script1", Code = "// v1" }]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Scripts = [new ResolvedScript { CanonicalName = "Script1", Code = "// v2" }]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.ScriptChanges);
        Assert.Equal(DiffChangeType.Changed, diff.ScriptChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_AttributeDescriptionChange_DetectedAsChanged()
    {
        // TemplateEngine-017: AttributesEqual must compare Description.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double", Description = "Original" }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double", Description = "Updated" }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.AttributeChanges);
        Assert.Equal(DiffChangeType.Changed, diff.AttributeChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_AttributeElementDataTypeChange_DetectedAsChanged()
    {
        // #290: ElementDataType is part of the revision hash, so AttributesEqual
        // must also compare it — a List element-type change (Int32 → Double) with
        // identical JSON-encoded values must surface as a Changed attribute,
        // keeping the structured diff in parity with the staleness hash.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Readings", Value = "[1,2]", DataType = "List", ElementDataType = "Int32" }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes =
            [
                new ResolvedAttribute { CanonicalName = "Readings", Value = "[1,2]", DataType = "List", ElementDataType = "Double" }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.AttributeChanges);
        Assert.Equal(DiffChangeType.Changed, diff.AttributeChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_AlarmDescriptionChange_DetectedAsChanged()
    {
        // TemplateEngine-017: AlarmsEqual must compare Description.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Alarms =
            [
                new ResolvedAlarm { CanonicalName = "HighTemp", TriggerType = "RangeViolation", Description = "Original" }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Alarms =
            [
                new ResolvedAlarm { CanonicalName = "HighTemp", TriggerType = "RangeViolation", Description = "Updated" }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.AlarmChanges);
        Assert.Equal(DiffChangeType.Changed, diff.AlarmChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_NativeAlarmSourceReferenceChange_DetectedAsChanged()
    {
        // PLAN-05 Task 12: native alarm source edits must surface in the diff so
        // the Deployments diff view is not blind to native-alarm binding changes.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA" }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpB" }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.NativeAlarmSourceChanges);
        Assert.Equal(DiffChangeType.Changed, diff.NativeAlarmSourceChanges[0].ChangeType);
        Assert.Equal("PumpAlarms", diff.NativeAlarmSourceChanges[0].CanonicalName);
    }

    [Fact]
    public void ComputeDiff_NativeAlarmSourceAdded_DetectedAsAdded()
    {
        var oldConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA" }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.NativeAlarmSourceChanges);
        Assert.Equal(DiffChangeType.Added, diff.NativeAlarmSourceChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_NativeAlarmSourceRemoved_DetectedAsRemoved()
    {
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA" }
            ]
        };
        var newConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.NativeAlarmSourceChanges);
        Assert.Equal(DiffChangeType.Removed, diff.NativeAlarmSourceChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_NativeAlarmSourceLockChange_DetectedAsChanged()
    {
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA", IsLocked = false }
            ]
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            NativeAlarmSources =
            [
                new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA", IsLocked = true }
            ]
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.Single(diff.NativeAlarmSourceChanges);
        Assert.Equal(DiffChangeType.Changed, diff.NativeAlarmSourceChanges[0].ChangeType);
    }

    [Fact]
    public void ComputeDiff_NativeAlarmSourceUnchanged_NoDiff()
    {
        var source = new ResolvedNativeAlarmSource { CanonicalName = "PumpAlarms", ConnectionName = "Opc1", SourceReference = "ns=2;s=PumpA", ConditionFilter = "HighHigh" };
        var oldConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1", NativeAlarmSources = [source] };
        var newConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1", NativeAlarmSources = [source with { }] };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.Empty(diff.NativeAlarmSourceChanges);
    }

    [Fact]
    public void ConnectionsEqual_IdenticalConfigs_ReturnsTrue()
    {
        // TemplateEngine-017: ConnectionsEqual is the comparator callers use
        // to detect connection-endpoint drift (the diff-view extension that
        // surfaces this in the UI is tracked under TemplateEngine-018).
        var a = new ConnectionConfig
        {
            Protocol = "OpcUa",
            ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-a\"}",
            BackupConfigurationJson = "{\"endpoint\":\"opc.tcp://host-b\"}",
            FailoverRetryCount = 3
        };
        var b = a with { };

        Assert.True(DiffService.ConnectionsEqual(a, b));
    }

    [Fact]
    public void ConnectionsEqual_EndpointEdit_ReturnsFalse()
    {
        // TemplateEngine-017: primary endpoint JSON edit must surface as a
        // change. Without this, deployment redeploys ship a different
        // ConnectionConfig with no visible drift signal.
        var a = new ConnectionConfig
        {
            Protocol = "OpcUa",
            ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-a:4840\"}",
            FailoverRetryCount = 3
        };
        var b = a with { ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-b:4840\"}" };

        Assert.False(DiffService.ConnectionsEqual(a, b));
    }

    [Fact]
    public void ConnectionsEqual_BackupConfigurationEdit_ReturnsFalse()
    {
        var a = new ConnectionConfig { Protocol = "OpcUa", ConfigurationJson = "{}", BackupConfigurationJson = null, FailoverRetryCount = 3 };
        var b = a with { BackupConfigurationJson = "{\"endpoint\":\"opc.tcp://backup\"}" };

        Assert.False(DiffService.ConnectionsEqual(a, b));
    }

    [Fact]
    public void ConnectionsEqual_FailoverRetryCountEdit_ReturnsFalse()
    {
        var a = new ConnectionConfig { Protocol = "OpcUa", ConfigurationJson = "{}", FailoverRetryCount = 3 };
        var b = a with { FailoverRetryCount = 5 };

        Assert.False(DiffService.ConnectionsEqual(a, b));
    }

    [Fact]
    public void ConnectionsEqual_ProtocolEdit_ReturnsFalse()
    {
        var a = new ConnectionConfig { Protocol = "OpcUa", ConfigurationJson = "{}", FailoverRetryCount = 3 };
        var b = a with { Protocol = "Modbus" };

        Assert.False(DiffService.ConnectionsEqual(a, b));
    }

    // ── TemplateEngine-018: ComputeConnectionsDiff produces Added/Removed/Changed entries ──

    [Fact]
    public void ComputeConnectionsDiff_NewBindingAdded_ReportedAsAdded()
    {
        // First-time binding (or instance gains its first data-sourced
        // attribute) — old config has no Connections map, new config does.
        // The pre-018 diff shape silently dropped this so operators saw
        // "no changes" when the deployment package was structurally larger.
        var oldConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host\"}",
                    FailoverRetryCount = 3,
                }
            }
        };

        var diff = _sut.ComputeConnectionsDiff(oldConfig, newConfig);

        Assert.Single(diff);
        Assert.Equal("plc1", diff[0].CanonicalName);
        Assert.Equal(DiffChangeType.Added, diff[0].ChangeType);
        Assert.Null(diff[0].OldValue);
        Assert.NotNull(diff[0].NewValue);
        Assert.Equal("OpcUa", diff[0].NewValue!.Protocol);
    }

    [Fact]
    public void ComputeConnectionsDiff_NullOldConfig_AllReportedAsAdded()
    {
        // First deploy: there is no prior flattened config at all (null), so
        // every connection in the new config is Added. Exercises the public
        // method's null-oldConfig tolerance explicitly (the ComputeDiff path
        // covers it end-to-end, but the isolated API contract is asserted here).
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host\"}",
                    FailoverRetryCount = 3,
                }
            }
        };

        var diff = _sut.ComputeConnectionsDiff(null, newConfig);

        Assert.Single(diff);
        Assert.Equal("plc1", diff[0].CanonicalName);
        Assert.Equal(DiffChangeType.Added, diff[0].ChangeType);
        Assert.Null(diff[0].OldValue);
        Assert.Equal("OpcUa", diff[0].NewValue!.Protocol);
    }

    [Fact]
    public void ComputeConnectionsDiff_BindingCleared_ReportedAsRemoved()
    {
        // Last data-sourced attribute removed — old config carried a
        // connection, new config does not.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig { Protocol = "OpcUa", ConfigurationJson = "{}" }
            }
        };
        var newConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1" };

        var diff = _sut.ComputeConnectionsDiff(oldConfig, newConfig);

        Assert.Single(diff);
        Assert.Equal("plc1", diff[0].CanonicalName);
        Assert.Equal(DiffChangeType.Removed, diff[0].ChangeType);
        Assert.NotNull(diff[0].OldValue);
        Assert.Null(diff[0].NewValue);
    }

    [Fact]
    public void ComputeConnectionsDiff_EndpointEdit_ReportedAsChanged()
    {
        // A connection-endpoint edit must surface as a Changed diff entry —
        // the deployment package will ship a different ConnectionConfig and
        // the operator-facing diff view must say so.
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-a:4840\"}",
                    FailoverRetryCount = 3,
                }
            }
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-b:4840\"}",
                    FailoverRetryCount = 3,
                }
            }
        };

        var diff = _sut.ComputeConnectionsDiff(oldConfig, newConfig);

        Assert.Single(diff);
        Assert.Equal("plc1", diff[0].CanonicalName);
        Assert.Equal(DiffChangeType.Changed, diff[0].ChangeType);
        Assert.Contains("host-a", diff[0].OldValue!.ConfigurationJson);
        Assert.Contains("host-b", diff[0].NewValue!.ConfigurationJson);
    }

    [Fact]
    public void ComputeConnectionsDiff_IdenticalConnections_NoEntries()
    {
        // Sanity check: an unchanged connection produces no diff entry, so
        // ComputeConnectionsDiff stays quiet when nothing relevant has
        // changed.
        var connections = new Dictionary<string, ConnectionConfig>
        {
            ["plc1"] = new ConnectionConfig { Protocol = "OpcUa", ConfigurationJson = "{}" }
        };
        var oldConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1", Connections = connections };
        var newConfig = new FlattenedConfiguration { InstanceUniqueName = "Instance1", Connections = connections };

        var diff = _sut.ComputeConnectionsDiff(oldConfig, newConfig);

        Assert.Empty(diff);
    }

    // ── TemplateEngine-018: ComputeDiff wires ComputeConnectionsDiff into the
    // public ConfigurationDiff.ConnectionChanges slot so standalone connection
    // protocol/endpoint/failover drift surfaces in the deployment diff (#10). ──

    [Fact]
    public void ComputeDiff_ConnectionProtocolAndEndpointAndFailoverChange_PopulatesConnectionChanges()
    {
        // Protocol, endpoint config JSON, and failover retry count all differ
        // on the same connection. Before this wiring, ComputeDiff dropped the
        // entire connection dimension so this redeploy showed "no changes".
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-a:4840\"}",
                    FailoverRetryCount = 3,
                }
            }
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "Modbus",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-b:4840\"}",
                    FailoverRetryCount = 5,
                }
            }
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Single(diff.ConnectionChanges);
        var entry = diff.ConnectionChanges[0];
        Assert.Equal("plc1", entry.CanonicalName);
        Assert.Equal(DiffChangeType.Changed, entry.ChangeType);
        Assert.Equal("OpcUa", entry.OldValue!.Protocol);
        Assert.Equal("Modbus", entry.NewValue!.Protocol);
        Assert.Contains("host-a", entry.OldValue!.ConfigurationJson);
        Assert.Contains("host-b", entry.NewValue!.ConfigurationJson);
        Assert.Equal(3, entry.OldValue!.FailoverRetryCount);
        Assert.Equal(5, entry.NewValue!.FailoverRetryCount);
    }

    [Fact]
    public void ComputeDiff_OnlyConnectionDiffers_HasChangesIsTrue()
    {
        // Attributes, alarms, and scripts are identical; only a connection's
        // endpoint changed. HasChanges must be true so the diff view does not
        // claim "no differences" while a connection endpoint silently moved.
        var attributes = new List<ResolvedAttribute>
        {
            new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }
        };
        var oldConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = attributes,
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-a:4840\"}",
                    FailoverRetryCount = 3,
                }
            }
        };
        var newConfig = new FlattenedConfiguration
        {
            InstanceUniqueName = "Instance1",
            Attributes = attributes,
            Connections = new Dictionary<string, ConnectionConfig>
            {
                ["plc1"] = new ConnectionConfig
                {
                    Protocol = "OpcUa",
                    ConfigurationJson = "{\"endpoint\":\"opc.tcp://host-b:4840\"}",
                    FailoverRetryCount = 3,
                }
            }
        };

        var diff = _sut.ComputeDiff(oldConfig, newConfig);

        Assert.True(diff.HasChanges);
        Assert.Empty(diff.AttributeChanges);
        Assert.Empty(diff.AlarmChanges);
        Assert.Empty(diff.ScriptChanges);
        Assert.Single(diff.ConnectionChanges);
        Assert.Equal(DiffChangeType.Changed, diff.ConnectionChanges[0].ChangeType);
    }
}
