using ScadaLink.Commons.Types.Enums;

namespace ScadaLink.HealthMonitoring.Tests;

public class SiteHealthCollectorTests
{
    private readonly SiteHealthCollector _collector = new();

    [Fact]
    public void CollectReport_ReturnsZeroCounters_WhenNoErrorsRecorded()
    {
        var report = _collector.CollectReport("site-1");

        Assert.Equal("site-1", report.SiteId);
        Assert.Equal(0, report.ScriptErrorCount);
        Assert.Equal(0, report.AlarmEvaluationErrorCount);
        Assert.Equal(0, report.DeadLetterCount);
    }

    [Fact]
    public void IncrementScriptError_AccumulatesBetweenReports()
    {
        _collector.IncrementScriptError();
        _collector.IncrementScriptError();
        _collector.IncrementScriptError();

        var report = _collector.CollectReport("site-1");
        Assert.Equal(3, report.ScriptErrorCount);
    }

    [Fact]
    public void IncrementAlarmError_AccumulatesBetweenReports()
    {
        _collector.IncrementAlarmError();
        _collector.IncrementAlarmError();

        var report = _collector.CollectReport("site-1");
        Assert.Equal(2, report.AlarmEvaluationErrorCount);
    }

    [Fact]
    public void IncrementDeadLetter_AccumulatesBetweenReports()
    {
        _collector.IncrementDeadLetter();

        var report = _collector.CollectReport("site-1");
        Assert.Equal(1, report.DeadLetterCount);
    }

    [Fact]
    public void CollectReport_ResetsCounters_AfterCollection()
    {
        _collector.IncrementScriptError();
        _collector.IncrementAlarmError();
        _collector.IncrementDeadLetter();

        var first = _collector.CollectReport("site-1");
        Assert.Equal(1, first.ScriptErrorCount);
        Assert.Equal(1, first.AlarmEvaluationErrorCount);
        Assert.Equal(1, first.DeadLetterCount);

        var second = _collector.CollectReport("site-1");
        Assert.Equal(0, second.ScriptErrorCount);
        Assert.Equal(0, second.AlarmEvaluationErrorCount);
        Assert.Equal(0, second.DeadLetterCount);
    }

    [Fact]
    public void UpdateConnectionHealth_ReflectedInReport()
    {
        _collector.UpdateConnectionHealth("opc-1", ConnectionHealth.Connected);
        _collector.UpdateConnectionHealth("opc-2", ConnectionHealth.Disconnected);

        var report = _collector.CollectReport("site-1");

        Assert.Equal(2, report.DataConnectionStatuses.Count);
        Assert.Equal(ConnectionHealth.Connected, report.DataConnectionStatuses["opc-1"]);
        Assert.Equal(ConnectionHealth.Disconnected, report.DataConnectionStatuses["opc-2"]);
    }

    [Fact]
    public void ConnectionHealth_NotResetAfterCollect()
    {
        _collector.UpdateConnectionHealth("opc-1", ConnectionHealth.Connected);

        _collector.CollectReport("site-1");
        var second = _collector.CollectReport("site-1");

        Assert.Single(second.DataConnectionStatuses);
        Assert.Equal(ConnectionHealth.Connected, second.DataConnectionStatuses["opc-1"]);
    }

    [Fact]
    public void RemoveConnection_RemovesFromReport()
    {
        _collector.UpdateConnectionHealth("opc-1", ConnectionHealth.Connected);
        _collector.UpdateTagResolution("opc-1", 10, 8);
        _collector.RemoveConnection("opc-1");

        var report = _collector.CollectReport("site-1");
        Assert.Empty(report.DataConnectionStatuses);
        Assert.Empty(report.TagResolutionCounts);
    }

    [Fact]
    public void UpdateTagResolution_ReflectedInReport()
    {
        _collector.UpdateTagResolution("opc-1", 50, 45);

        var report = _collector.CollectReport("site-1");

        Assert.Single(report.TagResolutionCounts);
        Assert.Equal(50, report.TagResolutionCounts["opc-1"].TotalSubscribed);
        Assert.Equal(45, report.TagResolutionCounts["opc-1"].SuccessfullyResolved);
    }

    [Fact]
    public void StoreAndForwardBufferDepths_IsEmptyPlaceholder()
    {
        var report = _collector.CollectReport("site-1");
        Assert.Empty(report.StoreAndForwardBufferDepths);
    }

    [Fact]
    public void CollectReport_IncludesUtcTimestamp()
    {
        var before = DateTimeOffset.UtcNow;
        var report = _collector.CollectReport("site-1");
        var after = DateTimeOffset.UtcNow;

        Assert.InRange(report.ReportTimestamp, before, after);
    }

    [Fact]
    public void CollectReport_SequenceNumberIsZero_CallerAssignsIt()
    {
        var report = _collector.CollectReport("site-1");
        Assert.Equal(0, report.SequenceNumber);
    }

    // HealthMonitoring-009 regression: the remaining collector setters had no
    // "reflected in report" coverage. The following tests verify each setter's
    // value reaches CollectReport output.

    [Fact]
    public void SetClusterNodes_ReflectedInReport()
    {
        var nodes = new List<ScadaLink.Commons.Messages.Health.NodeStatus>
        {
            new("node-a", true, "Active"),
            new("node-b", true, "Standby")
        };
        _collector.SetClusterNodes(nodes);

        var report = _collector.CollectReport("site-1");

        Assert.NotNull(report.ClusterNodes);
        Assert.Equal(2, report.ClusterNodes!.Count);
        Assert.Equal("node-a", report.ClusterNodes[0].Hostname);
    }

    [Fact]
    public void SetInstanceCounts_ReflectedInReport()
    {
        _collector.SetInstanceCounts(deployed: 10, enabled: 7, disabled: 3);

        var report = _collector.CollectReport("site-1");

        Assert.Equal(10, report.DeployedInstanceCount);
        Assert.Equal(7, report.EnabledInstanceCount);
        Assert.Equal(3, report.DisabledInstanceCount);
    }

    [Fact]
    public void SetParkedMessageCount_ReflectedInReport()
    {
        _collector.SetParkedMessageCount(42);

        var report = _collector.CollectReport("site-1");

        Assert.Equal(42, report.ParkedMessageCount);
    }

    [Fact]
    public void SetNodeHostname_ReflectedInReport()
    {
        _collector.SetNodeHostname("site-host-1");

        var report = _collector.CollectReport("site-1");

        Assert.Equal("site-host-1", report.NodeHostname);
    }

    [Fact]
    public void SetActiveNode_ReflectedInNodeRole()
    {
        _collector.SetActiveNode(true);
        Assert.Equal("Active", _collector.CollectReport("site-1").NodeRole);
        Assert.True(_collector.IsActiveNode);

        _collector.SetActiveNode(false);
        Assert.Equal("Standby", _collector.CollectReport("site-1").NodeRole);
        Assert.False(_collector.IsActiveNode);
    }

    [Fact]
    public void UpdateTagQuality_ReflectedInReport()
    {
        _collector.UpdateTagQuality("opc-1", good: 80, bad: 15, uncertain: 5);

        var report = _collector.CollectReport("site-1");

        Assert.NotNull(report.DataConnectionTagQuality);
        var quality = report.DataConnectionTagQuality!["opc-1"];
        Assert.Equal(80, quality.Good);
        Assert.Equal(15, quality.Bad);
        Assert.Equal(5, quality.Uncertain);
    }

    [Fact]
    public void UpdateConnectionEndpoint_ReflectedInReport()
    {
        _collector.UpdateConnectionEndpoint("opc-1", "opc.tcp://plc-1:4840");

        var report = _collector.CollectReport("site-1");

        Assert.NotNull(report.DataConnectionEndpoints);
        Assert.Equal("opc.tcp://plc-1:4840", report.DataConnectionEndpoints!["opc-1"]);
    }

    [Fact]
    public void SetStoreAndForwardDepths_ReflectedInReport()
    {
        _collector.SetStoreAndForwardDepths(new Dictionary<string, int>
        {
            ["ExternalSystem"] = 5,
            ["Notification"] = 2
        });

        var report = _collector.CollectReport("site-1");

        Assert.Equal(5, report.StoreAndForwardBufferDepths["ExternalSystem"]);
        Assert.Equal(2, report.StoreAndForwardBufferDepths["Notification"]);
    }

    [Fact]
    public async Task ThreadSafety_ConcurrentIncrements()
    {
        const int iterations = 10_000;
        var tasks = new[]
        {
            Task.Run(() => { for (int i = 0; i < iterations; i++) _collector.IncrementScriptError(); }),
            Task.Run(() => { for (int i = 0; i < iterations; i++) _collector.IncrementAlarmError(); }),
            Task.Run(() => { for (int i = 0; i < iterations; i++) _collector.IncrementDeadLetter(); })
        };

        await Task.WhenAll(tasks);

        var report = _collector.CollectReport("site-1");
        Assert.Equal(iterations, report.ScriptErrorCount);
        Assert.Equal(iterations, report.AlarmEvaluationErrorCount);
        Assert.Equal(iterations, report.DeadLetterCount);
    }
}
