// Copyright 2021-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/rate_counter.go in the NATS server Go source.

namespace ZB.MOM.NatsNet.Server.Internal;

/// <summary>
/// A sliding-window rate limiter that allows at most <c>limit</c> events
/// per <see cref="Interval"/> window.
/// Mirrors <c>rateCounter</c> in server/rate_counter.go.
/// </summary>
public sealed class RateCounter
{
    private readonly long _limit;
    private long _count;
    private ulong _blocked;
    private DateTime _end;

    // Exposed for tests (mirrors direct field access in rate_counter_test.go).
    public TimeSpan Interval;

    private readonly object _lock = new();

    public RateCounter(long limit)
    {
        _limit = limit;
        Interval = TimeSpan.FromSeconds(1);
    }

    /// <summary>
    /// Factory wrapper for Go parity.
    /// Mirrors <c>newRateCounter</c>.
    /// </summary>
    public static RateCounter NewRateCounter(long limit) => new(limit);

    /// <summary>
    /// Returns true if the event is within the rate limit for the current window.
    /// Mirrors <c>rateCounter.allow</c>.
    /// </summary>
    public bool Allow()
    {
        var now = DateTime.UtcNow;

        lock (_lock)
        {
            if (now > _end)
            {
                _count = 0;
                _end = now + Interval;
            }
            else
            {
                _count++;
            }

            var allow = _count < _limit;
            if (!allow)
                _blocked++;
            return allow;
        }
    }

    /// <summary>
    /// Returns and resets the count of blocked events since the last call.
    /// Mirrors <c>rateCounter.countBlocked</c>.
    /// </summary>
    public ulong CountBlocked()
    {
        lock (_lock)
        {
            var blocked = _blocked;
            _blocked = 0;
            return blocked;
        }
    }
}
