using System.Collections.Concurrent;

namespace Nexus.Api.RateLimiting;

/// <summary>
/// Simple in-memory tracking of login attempts per IP,
/// aligned with the fixed-window rate limiter (5 attempts / 1 minute).
///
/// Provides remaining-attempt count that can be passed back to the frontend.
/// </summary>
public sealed class LoginAttemptTracker
{
    private const int MaxAttempts = 5;
    private static readonly TimeSpan Window = TimeSpan.FromMinutes(1);

    // IP → (count, windowStartTicks)
    private static readonly ConcurrentDictionary<string, (int Count, long WindowStartTicks)> _store = new();

    /// <summary>
    /// Registers a failed attempt for the given IP.
    /// Returns remaining attempts (0 = locked out until reset).
    /// </summary>
    public int RecordFailedAttempt(string ip)
    {
        var now = Environment.TickCount64;
        var windowTicks = (long)Window.TotalMilliseconds;

        var (count, windowStart) = _store.AddOrUpdate(ip,
            _ => (1, now),
            (_, entry) =>
            {
                if (now - entry.WindowStartTicks >= windowTicks)
                    return (1, now);
                return (entry.Count + 1, entry.WindowStartTicks);
            });

        return Math.Max(0, MaxAttempts - count);
    }

    /// <summary>
    /// Returns the remaining attempts for the given IP without recording.
    /// </summary>
    public int GetRemaining(string ip)
    {
        var now = Environment.TickCount64;
        var windowTicks = (long)Window.TotalMilliseconds;

        if (_store.TryGetValue(ip, out var entry))
        {
            if (now - entry.WindowStartTicks >= windowTicks)
                return MaxAttempts;
            return Math.Max(0, MaxAttempts - entry.Count);
        }

        return MaxAttempts;
    }

    /// <summary>
    /// Returns the number of seconds until the rate-limit window resets,
    /// or 0 if the window has already expired / no attempts recorded.
    /// </summary>
    public int GetRetryAfterSeconds(string ip)
    {
        var now = Environment.TickCount64;
        var windowTicks = (long)Window.TotalMilliseconds;

        if (!_store.TryGetValue(ip, out var entry))
            return 0;

        var elapsed = now - entry.WindowStartTicks;
        if (elapsed >= windowTicks)
            return 0;

        return (int)Math.Ceiling((windowTicks - elapsed) / 1000.0);
    }

    /// <summary>
    /// Resets attempt count for the given IP (e.g. on success).
    /// </summary>
    public void Reset(string ip)
    {
        _store.TryRemove(ip, out _);
    }
}
