using Microsoft.Extensions.Logging.Abstractions;

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

/// <summary>
/// M2.16 (#30) — unit tests for <see cref="SiteEventLogFailureCountReporter"/>.
/// Verifies that the poller reads the count provided by the
/// <see cref="Func{TResult}"/> delegate and pushes it into
/// <see cref="ISiteHealthCollector.SetSiteEventLogWriteFailures"/>.
/// </summary>
public class SiteEventLogFailureCountReporterTests
{
    [Fact]
    public async Task StopAsync_AfterDispose_DoesNotThrow()
    {
        // Regression (Gitea #15 follow-up): Dispose tears down the CTS StopAsync
        // cancels, and the host does not guarantee StopAsync is driven before the DI
        // container is disposed. Cancel() on a disposed CTS throws, and letting that
        // escape an IHostedService aborts the host's whole shutdown sequence.
        var reporter = new SiteEventLogFailureCountReporter(
            failedWriteCountProvider: () => 0L,
            collector: new SiteHealthCollector(),
            logger: NullLogger<SiteEventLogFailureCountReporter>.Instance,
            refreshInterval: TimeSpan.FromHours(1));

        await reporter.StartAsync(CancellationToken.None);
        reporter.Dispose();

        // The assertion is the absence of ObjectDisposedException.
        await reporter.StopAsync(CancellationToken.None);
        reporter.Dispose();
    }

    [Fact]
    public async Task StartAsync_ImmediatelyProbes_FailedWriteCount()
    {
        // Arrange
        var count = 99L;
        var collector = new SiteHealthCollector();
        using var reporter = new SiteEventLogFailureCountReporter(
            failedWriteCountProvider: () => count,
            collector: collector,
            logger: NullLogger<SiteEventLogFailureCountReporter>.Instance,
            refreshInterval: TimeSpan.FromHours(1)); // long interval — only immediate tick matters

        // Act
        await reporter.StartAsync(CancellationToken.None);

        // Give the background Task a moment to execute its synchronous immediate probe.
        var deadline = DateTime.UtcNow.AddSeconds(5);
        while (collector.CollectReport("probe").SiteEventLogWriteFailures == 0L
               && DateTime.UtcNow < deadline)
        {
            await Task.Delay(10);
        }

        // Assert — the immediate probe before the first Delay must have fired.
        var report = collector.CollectReport("site-1");
        Assert.Equal(99L, report.SiteEventLogWriteFailures);

        await reporter.StopAsync(CancellationToken.None);
    }

    [Fact]
    public async Task StartAsync_PushesLatestCount_OnEachTick()
    {
        // Arrange — start with count 5; advance to 12 after the first tick.
        var count = 5L;
        var collector = new SiteHealthCollector();
        using var reporter = new SiteEventLogFailureCountReporter(
            failedWriteCountProvider: () => count,
            collector: collector,
            logger: NullLogger<SiteEventLogFailureCountReporter>.Instance,
            refreshInterval: TimeSpan.FromMilliseconds(50));

        await reporter.StartAsync(CancellationToken.None);

        // Wait for immediate probe.
        var deadline = DateTime.UtcNow.AddSeconds(5);
        while (collector.CollectReport("probe").SiteEventLogWriteFailures != 5L
               && DateTime.UtcNow < deadline)
            await Task.Delay(10);

        Assert.Equal(5L, collector.CollectReport("site-1").SiteEventLogWriteFailures);

        // Advance the counter and wait for the next tick to push the new value.
        count = 12L;

        deadline = DateTime.UtcNow.AddSeconds(5);
        while (collector.CollectReport("probe").SiteEventLogWriteFailures != 12L
               && DateTime.UtcNow < deadline)
            await Task.Delay(10);

        Assert.Equal(12L, collector.CollectReport("site-1").SiteEventLogWriteFailures);

        await reporter.StopAsync(CancellationToken.None);
    }
}
