using ScadaLink.Commons.Types;

namespace ScadaLink.Commons.Tests.Types;

public class StaleTagMonitorTests
{
    [Fact]
    public void Constructor_ZeroTimeSpan_Throws()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => new StaleTagMonitor(TimeSpan.Zero));
    }

    [Fact]
    public void Constructor_NegativeTimeSpan_Throws()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => new StaleTagMonitor(TimeSpan.FromSeconds(-1)));
    }

    [Fact]
    public async Task Stale_FiresAfterMaxSilence()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(100));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();

        await Task.Delay(300);
        Assert.Equal(1, staleCount);
    }

    [Fact]
    public async Task Stale_FiresOnlyOnce()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(50));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();

        await Task.Delay(300);
        Assert.Equal(1, staleCount);
    }

    [Fact]
    public async Task OnValueReceived_ResetsTimer()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(200));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();

        // Keep resetting before the 200ms deadline
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(100);
            monitor.OnValueReceived();
        }

        // Should not have gone stale
        Assert.Equal(0, staleCount);
    }

    [Fact]
    public async Task OnValueReceived_AllowsStaleAfterSilence()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(100));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();

        // Reset once
        await Task.Delay(50);
        monitor.OnValueReceived();

        // Then go silent
        await Task.Delay(250);
        Assert.Equal(1, staleCount);
    }

    [Fact]
    public async Task OnValueReceived_ResetsStaleFlag_AllowsSecondFire()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(100));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();

        // Wait for first stale
        await Task.Delay(250);
        Assert.Equal(1, staleCount);

        // Reset — should allow second stale fire
        monitor.OnValueReceived();
        await Task.Delay(250);
        Assert.Equal(2, staleCount);
    }

    [Fact]
    public async Task Stop_PreventsStale()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(50));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();
        monitor.Stop();

        await Task.Delay(200);
        Assert.Equal(0, staleCount);
    }

    [Fact]
    public async Task Dispose_PreventsStale()
    {
        var monitor = new StaleTagMonitor(TimeSpan.FromMilliseconds(50));
        var staleCount = 0;
        monitor.Stale += () => Interlocked.Increment(ref staleCount);
        monitor.Start();
        monitor.Dispose();

        await Task.Delay(200);
        Assert.Equal(0, staleCount);
    }

    [Fact]
    public void MaxSilence_ReturnsConfiguredValue()
    {
        using var monitor = new StaleTagMonitor(TimeSpan.FromSeconds(42));
        Assert.Equal(TimeSpan.FromSeconds(42), monitor.MaxSilence);
    }
}
