namespace ZB.MOM.NatsNet.Server.Internal;

/// <summary>
/// Provides an efficiently-cached Unix nanosecond timestamp updated every
/// <see cref="TickInterval"/> by a shared background timer.
/// Register before use and Unregister when done; the timer shuts down when all
/// registrants have unregistered.
/// </summary>
/// <remarks>
/// Mirrors the Go <c>ats</c> package. Intended for high-frequency cache
/// access-time reads that do not need sub-100ms precision.
/// </remarks>
public static class AccessTimeService
{
    /// <summary>How often the cached time is refreshed.</summary>
    public static readonly TimeSpan TickInterval = TimeSpan.FromMilliseconds(100);

    private static long _utime;
    private static long _refs;
    private static Timer? _timer;
    private static readonly object _lock = new();

    static AccessTimeService()
    {
        // Mirror Go's init(): nothing to pre-allocate in .NET.
    }

    /// <summary>
    /// Explicit init hook for Go parity.
    /// Mirrors package <c>init()</c> in server/ats/ats.go.
    /// This method is intentionally idempotent.
    /// </summary>
    public static void Init()
    {
        // Ensure a non-zero cached timestamp is present.
        var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
        Interlocked.CompareExchange(ref _utime, now, 0);
    }

    /// <summary>
    /// Registers a user. Starts the background timer when the first registrant calls this.
    /// Each call to <see cref="Register"/> must be paired with a call to <see cref="Unregister"/>.
    /// </summary>
    public static void Register()
    {
        var v = Interlocked.Increment(ref _refs);
        if (v == 1)
        {
            Interlocked.Exchange(ref _utime, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L);
            lock (_lock)
            {
                _timer?.Dispose();
                _timer = new Timer(_ =>
                {
                    Interlocked.Exchange(ref _utime, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L);
                }, null, TickInterval, TickInterval);
            }
        }
    }

    /// <summary>
    /// Unregisters a user. Stops the background timer when the last registrant calls this.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown when unregister is called more times than register.</exception>
    public static void Unregister()
    {
        var v = Interlocked.Decrement(ref _refs);
        if (v == 0)
        {
            lock (_lock)
            {
                _timer?.Dispose();
                _timer = null;
            }
        }
        else if (v < 0)
        {
            Interlocked.Exchange(ref _refs, 0);
            throw new InvalidOperationException("ats: unbalanced unregister for access time state");
        }
    }

    /// <summary>
    /// Returns the last cached Unix nanosecond timestamp.
    /// If no registrant is active, returns a fresh timestamp (avoids returning zero).
    /// </summary>
    public static long AccessTime()
    {
        var v = Interlocked.Read(ref _utime);
        if (v == 0)
        {
            v = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
            Interlocked.CompareExchange(ref _utime, v, 0);
            v = Interlocked.Read(ref _utime);
        }
        return v;
    }

    /// <summary>
    /// Resets all state. For testing only.
    /// </summary>
    internal static void Reset()
    {
        lock (_lock)
        {
            _timer?.Dispose();
            _timer = null;
        }
        Interlocked.Exchange(ref _refs, 0);
        Interlocked.Exchange(ref _utime, 0);
    }
}
