using Shouldly;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server.Tests.Internal;

/// <summary>
/// Tests for <see cref="AccessTimeService"/>, mirroring ats_test.go.
/// </summary>
[Collection("AccessTimeService")]
public sealed class AccessTimeServiceTests : IDisposable
{
    public AccessTimeServiceTests()
    {
        AccessTimeService.Reset();
    }

    public void Dispose()
    {
        AccessTimeService.Reset();
    }

    [Fact]
    public void NotRunningValue_ShouldReturnNonZero()
    {
        // Mirror: TestNotRunningValue
        // No registrants; AccessTime() must still return a non-zero value.
        var at = AccessTimeService.AccessTime();
        at.ShouldBeGreaterThan(0);

        // Should be stable (no background timer updating it).
        var atn = AccessTimeService.AccessTime();
        atn.ShouldBe(at);
    }

    [Fact]
    public async Task RegisterAndUnregister_ShouldManageLifetime()
    {
        // Mirror: TestRegisterAndUnregister

        AccessTimeService.Register();

        var at = AccessTimeService.AccessTime();
        at.ShouldBeGreaterThan(0);

        // Background timer should update the time.
        await Task.Delay(AccessTimeService.TickInterval * 3);
        var atn = AccessTimeService.AccessTime();
        if (atn <= at)
        {
            await Task.Delay(AccessTimeService.TickInterval);
            atn = AccessTimeService.AccessTime();
        }
        atn.ShouldBeGreaterThan(at);

        // Unregister; timer should stop.
        AccessTimeService.Unregister();
        await Task.Delay(AccessTimeService.TickInterval);

        at = AccessTimeService.AccessTime();
        await Task.Delay(AccessTimeService.TickInterval * 3);
        atn = AccessTimeService.AccessTime();
        atn.ShouldBe(at);

        // Re-register should restart the timer.
        AccessTimeService.Register();
        try
        {
            at = AccessTimeService.AccessTime();
            await Task.Delay(AccessTimeService.TickInterval * 3);
            atn = AccessTimeService.AccessTime();
            if (atn <= at)
            {
                await Task.Delay(AccessTimeService.TickInterval);
                atn = AccessTimeService.AccessTime();
            }
            atn.ShouldBeGreaterThan(at);
        }
        finally
        {
            AccessTimeService.Unregister();
        }
    }

    [Fact]
    public void UnbalancedUnregister_ShouldThrow()
    {
        // Mirror: TestUnbalancedUnregister
        Should.Throw<InvalidOperationException>(() => AccessTimeService.Unregister());
    }

    [Fact]
    public void Init_ShouldBeIdempotentAndNonThrowing()
    {
        Should.NotThrow(() => AccessTimeService.Init());
        var first = AccessTimeService.AccessTime();
        first.ShouldBeGreaterThan(0);

        Should.NotThrow(() => AccessTimeService.Init());
        var second = AccessTimeService.AccessTime();
        second.ShouldBeGreaterThan(0);
    }
}
