using System.Security.Claims;
using System.Text.Json;
using MongoDB.Driver;
using Nalu.Web.Data;
using Nalu.Web.Data.Models;

namespace Nalu.Web.Services;

public record CreditConsumeResult
{
    public bool Success { get; init; }
    public int CreditsConsumed { get; init; }
    public int CreditsUsed { get; init; }
    public int CreditsLimit { get; init; }
    public DateTime ResetAt { get; init; }
    public object? ErrorPayload { get; init; }

    public int CreditsRemaining => Math.Max(0, CreditsLimit - CreditsUsed);
}

public class CreditService(MongoDbContext db, IConfiguration config)
{
    public async Task<CreditConsumeResult> TryConsumeAsync(
        ClaimsPrincipal user, string validatorId, CancellationToken ct = default)
    {
        var apiKey = user.FindFirst("api_key")?.Value ?? "";
        var plan   = user.FindFirst("plan")?.Value ?? "free";
        var cost   = CreditCosts.Get(validatorId);

        var limit = GetPlanLimit(plan);
        var now   = DateTime.UtcNow;
        var yearMonth = now.ToString("yyyy-MM");
        var resetAt   = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc)
                            .AddMonths(1);

        if (!db.IsConnected)
        {
            // No MongoDB — allow all requests (dev/test mode)
            return new CreditConsumeResult
            {
                Success = true, CreditsConsumed = cost, CreditsUsed = cost,
                CreditsLimit = limit, ResetAt = resetAt
            };
        }

        // Read current usage
        var current = await db.UsageMonthly
            .Find(u => u.ApiKey == apiKey && u.YearMonth == yearMonth)
            .FirstOrDefaultAsync(ct);

        var currentTotal = current?.TotalCreditsUsed ?? 0;

        if (limit > 0 && currentTotal + cost > limit)
        {
            return new CreditConsumeResult
            {
                Success = false,
                CreditsConsumed = 0,
                CreditsUsed = currentTotal,
                CreditsLimit = limit,
                ResetAt = resetAt,
                ErrorPayload = Build429Body(plan, currentTotal, limit, resetAt)
            };
        }

        // Atomic increment
        var filter = Builders<UsageMonthly>.Filter.And(
            Builders<UsageMonthly>.Filter.Eq(u => u.ApiKey, apiKey),
            Builders<UsageMonthly>.Filter.Eq(u => u.YearMonth, yearMonth));

        var update = Builders<UsageMonthly>.Update
            .Inc(u => u.TotalCreditsUsed, cost)
            .Inc(u => u.TotalRequests, 1)
            .Inc($"credits_by_validator.{validatorId}", cost)
            .Inc($"requests_by_validator.{validatorId}", 1)
            .Set(u => u.UpdatedAt, now)
            .SetOnInsert(u => u.Plan, plan)
            .SetOnInsert(u => u.ApiKey, apiKey)
            .SetOnInsert(u => u.YearMonth, yearMonth);

        var opts = new FindOneAndUpdateOptions<UsageMonthly>
        {
            IsUpsert = true,
            ReturnDocument = ReturnDocument.After
        };

        var after = await db.UsageMonthly.FindOneAndUpdateAsync(filter, update, opts, ct);

        return new CreditConsumeResult
        {
            Success = true,
            CreditsConsumed = cost,
            CreditsUsed = after.TotalCreditsUsed,
            CreditsLimit = limit,
            ResetAt = resetAt
        };
    }

    public void ApplyHeaders(HttpContext ctx, CreditConsumeResult result)
    {
        ctx.Response.Headers["X-Credits-Used"]      = result.CreditsConsumed.ToString();
        ctx.Response.Headers["X-Credits-Remaining"] = result.CreditsRemaining.ToString();
        ctx.Response.Headers["X-Credits-Limit"]     = result.CreditsLimit.ToString();
        ctx.Response.Headers["X-Credits-Reset"]     = result.ResetAt.ToString("O");
    }

    private int GetPlanLimit(string plan)
    {
        var v = config.GetValue<int?>($"Plans:{plan}:credits_per_month");
        return v ?? 0; // 0 = unlimited
    }

    private static object Build429Body(string plan, int used, int limit, DateTime resetAt) => new
    {
        error = "credits_exhausted",
        message = $"Seus créditos do mês acabaram. Seu plano ({Capitalize(plan)}) permite {limit:N0} créditos/mês.",
        credits_used = used,
        credits_limit = limit,
        reset_at = resetAt.ToString("O"),
        upgrade_url = "https://naluai.dev/precos",
        hint = "Upgrade para Starter por apenas R$ 0,0019 por validação. Menos que uma gota de café."
    };

    private static string Capitalize(string s) =>
        s.Length == 0 ? s : char.ToUpperInvariant(s[0]) + s[1..];
}
