using Predictalytics.Application.Interfaces;
using Predictalytics.Domain.Enums;
using System.Collections.Concurrent;

namespace Predictalytics.Application.Services;

/// <summary>
/// Token-bucket rate limiter with per-platform configuration.
/// </summary>
public class RateLimiterService : IRateLimiter
{
    private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphores = new();
    private readonly ConcurrentDictionary<string, DateTime> _lastRequest = new();
    private readonly ConcurrentDictionary<string, DateTime> _blockedUntil = new();

    private static readonly Dictionary<string, int> Delays = new()
    {
        { "Polymarket-Gamma", 35 },     // ~28 req/s (Gamma /markets limit is 30/s)
        { "Polymarket-Data", 55 },      // ~18 req/s (Data /trades limit is 20/s)
        { "Polymarket-Clob", 15 },      // ~66 req/s (Clob /prices limit is 100/s)
        { "Polymarket-Default", 0 },    // Bypass worker-level limiting for Polymarket, handled in ApiClient
        
        { "Limitless-Default", 500 },
        { "Azuro-Default", 1000 },
        { "Myriad-Default", 1000 },
        { "PredictFun-Default", 1000 },
        { "Kalshi-Default", 500 },
        { "Stake-Default", 1000 }
    };

    public async Task WaitAsync(PlatformType platform, CancellationToken ct = default, string endpointGroup = "Default", string? channelId = null)
    {
        var configKey = $"{platform}-{endpointGroup}";
        if (!Delays.ContainsKey(configKey)) configKey = $"{platform}-Default";

        var key = string.IsNullOrEmpty(channelId) ? configKey : $"{configKey}-{channelId}";

        var sem = _semaphores.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
        await sem.WaitAsync(ct);
        try
        {
            if (_blockedUntil.TryGetValue(key, out var blockedUntil))
            {
                var waitTime = blockedUntil - DateTime.UtcNow;
                if (waitTime > TimeSpan.Zero)
                {
                    await Task.Delay(waitTime, ct);
                }
            }

            if (_lastRequest.TryGetValue(key, out var last))
            {
                var delayMs = Delays.GetValueOrDefault(configKey, 1000);
                var elapsed = (DateTime.UtcNow - last).TotalMilliseconds;
                if (elapsed < delayMs)
                    await Task.Delay((int)(delayMs - elapsed), ct);
            }
            _lastRequest[key] = DateTime.UtcNow;
        }
        finally { sem.Release(); }
    }

    public bool CanMakeRequest(PlatformType platform, string endpointGroup = "Default", string? channelId = null)
    {
        var configKey = $"{platform}-{endpointGroup}";
        if (!Delays.ContainsKey(configKey)) configKey = $"{platform}-Default";

        var key = string.IsNullOrEmpty(channelId) ? configKey : $"{configKey}-{channelId}";

        if (_blockedUntil.TryGetValue(key, out var blockedUntil) && blockedUntil > DateTime.UtcNow)
            return false;

        if (!_lastRequest.TryGetValue(key, out var last)) return true;
        var delayMs = Delays.GetValueOrDefault(configKey, 1000);
        return (DateTime.UtcNow - last).TotalMilliseconds >= delayMs;
    }

    public void ReportRateLimitExceeded(PlatformType platform, TimeSpan? retryAfter = null, string endpointGroup = "Default", string? channelId = null)
    {
        var configKey = $"{platform}-{endpointGroup}";
        if (!Delays.ContainsKey(configKey)) configKey = $"{platform}-Default";

        var key = string.IsNullOrEmpty(channelId) ? configKey : $"{configKey}-{channelId}";

        var penalty = retryAfter ?? TimeSpan.FromSeconds(30);
        _blockedUntil[key] = DateTime.UtcNow.Add(penalty);
    }
}
