using System.Collections.Concurrent;
using Nalu.Web.Data.Repositories;

namespace Nalu.Web.Services;

public class RateLimitService(IConfiguration config, UsageRepository usageRepo)
{
    private readonly ConcurrentDictionary<string, InMemoryUsage> _fallback = new();

    /// <summary>
    /// Returns true and increments counters if the key is within limits.
    /// Uses MongoDB when connected; falls back to in-memory.
    /// </summary>
    public async Task<bool> TryConsumeAsync(string apiKey, string plan, CancellationToken ct = default)
    {
        var planSection = config.GetSection($"Plans:{plan}");
        var dailyLimit = planSection.GetValue<int?>("daily_limit");
        var monthlyLimit = planSection.GetValue<int?>("monthly_limit");

        // No limits configured — always allow
        if (!dailyLimit.HasValue && !monthlyLimit.HasValue) return true;

        var mongoResult = await usageRepo.IncrementAsync(apiKey, plan, ct);

        if (mongoResult.HasValue)
        {
            var (daily, monthly) = mongoResult.Value;
            if (dailyLimit.HasValue && daily > dailyLimit.Value) return false;
            if (monthlyLimit.HasValue && monthly > monthlyLimit.Value) return false;
            return true;
        }

        // Fallback: in-memory
        return TryConsumeFallback(apiKey, dailyLimit, monthlyLimit);
    }

    // Kept for sync callers and backwards compat during migration
    public bool TryConsume(string apiKey, string plan)
    {
        var planSection = config.GetSection($"Plans:{plan}");
        var dailyLimit = planSection.GetValue<int?>("daily_limit");
        var monthlyLimit = planSection.GetValue<int?>("monthly_limit");

        if (!dailyLimit.HasValue && !monthlyLimit.HasValue) return true;

        return TryConsumeFallback(apiKey, dailyLimit, monthlyLimit);
    }

    private bool TryConsumeFallback(string apiKey, int? dailyLimit, int? monthlyLimit)
    {
        var now = DateTime.UtcNow;
        var today = DateOnly.FromDateTime(now);
        var month = new YearMonth(now.Year, now.Month);

        var record = _fallback.GetOrAdd(apiKey, _ => new InMemoryUsage(today, month));

        lock (record)
        {
            if (record.Day != today) { record.Day = today; record.DailyCount = 0; }
            if (record.Month != month) { record.Month = month; record.MonthlyCount = 0; }

            if (dailyLimit.HasValue && record.DailyCount >= dailyLimit.Value) return false;
            if (monthlyLimit.HasValue && record.MonthlyCount >= monthlyLimit.Value) return false;

            record.DailyCount++;
            record.MonthlyCount++;
            return true;
        }
    }

    private sealed class InMemoryUsage(DateOnly day, YearMonth month)
    {
        public DateOnly Day { get; set; } = day;
        public int DailyCount { get; set; }
        public YearMonth Month { get; set; } = month;
        public int MonthlyCount { get; set; }
    }

    private record YearMonth(int Year, int Month);
}
