using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using QRRapidoApp.Models;
using QRRapidoApp.Services;

namespace QRRapidoApp.Filters
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class ApiKeyAuthorizeAttribute : Attribute, IAsyncActionFilter
    {
        private const string ApiKeyHeaderName        = "X-API-Key";
        private const string RapidApiSecretHeaderName = "X-RapidAPI-Proxy-Secret";
        private const string RapidApiUserHeaderName   = "X-RapidAPI-User";

        // Tracks 429 events per key for abuse logging (key: prefix, value: list of timestamps)
        // In-process only; acceptable for the abuse detection use case.
        private static readonly System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Generic.Queue<DateTime>> _abuseTracker = new();

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // [AllowAnonymous] on the action bypasses this filter
            var hasAllowAnonymous = context.ActionDescriptor.EndpointMetadata
                .OfType<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>()
                .Any();
            if (hasAllowAnonymous)
            {
                await next();
                return;
            }

            var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiKeyAuthorizeAttribute>>();

            try
            {
                // ── RapidAPI flow ────────────────────────────────────────────
                var config = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
                var expectedSecret = config["RapidApi:ProxySecret"];

                if (!string.IsNullOrWhiteSpace(expectedSecret) &&
                    context.HttpContext.Request.Headers.TryGetValue(RapidApiSecretHeaderName, out var incomingSecret) &&
                    string.Equals(incomingSecret, expectedSecret, StringComparison.Ordinal))
                {
                    var rapidApiUser = context.HttpContext.Request.Headers[RapidApiUserHeaderName].ToString();
                    context.HttpContext.Items["AuthSource"]   = "RapidAPI";
                    context.HttpContext.Items["RapidApiUser"] = rapidApiUser;
                    context.HttpContext.Items["ApiPlanTier"]  = ApiPlanTier.Pro;
                    logger.LogInformation("RapidAPI request authorized. RapidAPI-User: {User}", rapidApiUser);
                    await next();
                    return;
                }

                // ── Direct X-API-Key flow ────────────────────────────────────
                if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))
                {
                    logger.LogWarning("API Key missing in request headers from {IP}", GetIp(context));
                    context.Result = JsonError(401, "Unauthorized. Provide a valid X-API-Key header or use the RapidAPI marketplace.");
                    return;
                }

                var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
                var user = await userService.GetUserByApiKeyAsync(extractedApiKey!);

                if (user == null)
                {
                    var masked = MaskKey(extractedApiKey.ToString());
                    logger.LogWarning("Invalid API Key {Masked} from {IP}", masked, GetIp(context));
                    context.Result = JsonError(401, "Unauthorized. Invalid or revoked API Key.");
                    return;
                }

                // Find the matching ApiKeyConfig by prefix (first 8 chars of raw key)
                var rawKey = extractedApiKey.ToString();
                var prefix = rawKey.Length >= 8 ? rawKey[..8] : rawKey;
                var apiKeyConfig = user.ApiKeys.FirstOrDefault(k => k.Prefix == prefix && k.IsActive);

                if (apiKeyConfig == null)
                {
                    context.Result = JsonError(403, "API Key is revoked or not found.");
                    return;
                }

                // Plan tier comes from the user's active API subscription, not from the key config.
                // This ensures that upgrading/downgrading immediately affects all keys.
                var effectiveTier = user.ApiSubscription?.EffectiveTier ?? ApiPlanTier.Free;

                // Rate limit check
                var rateLimitService = context.HttpContext.RequestServices.GetRequiredService<IApiRateLimitService>();
                var rl = await rateLimitService.CheckAndIncrementAsync(prefix, effectiveTier);

                // Always inject rate limit headers
                AddRateLimitHeaders(context.HttpContext, rl);

                if (!rl.Allowed)
                {
                    var ip = GetIp(context);
                    TrackAbuse(prefix, logger, ip);

                    var message = rl.MonthlyExceeded
                        ? $"Monthly quota exceeded ({rl.MonthlyLimit} requests). Upgrade your plan at qrrapido.site/Developer."
                        : $"Rate limit exceeded ({rl.PerMinuteLimit} req/min). Retry after {rl.ResetUnixTimestamp - DateTimeOffset.UtcNow.ToUnixTimeSeconds()}s.";

                    context.HttpContext.Response.Headers["Retry-After"] =
                        (rl.ResetUnixTimestamp - DateTimeOffset.UtcNow.ToUnixTimeSeconds()).ToString();

                    context.Result = JsonError(429, message, new
                    {
                        plan = ApiPlanLimits.PlanName(apiKeyConfig.PlanTier),
                        upgradeUrl = "https://qrrapido.site/Developer"
                    });
                    return;
                }

                context.HttpContext.Items["ApiKeyUserId"] = user.Id;
                context.HttpContext.Items["ApiKeyPrefix"] = prefix;
                context.HttpContext.Items["ApiPlanTier"]  = effectiveTier;

                await next();
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error during API Key authorization.");
                context.Result = JsonError(500, "Internal server error during authorization.");
            }
        }

        // ── helpers ──────────────────────────────────────────────────────────

        private static void AddRateLimitHeaders(HttpContext ctx, RateLimitResult rl)
        {
            if (rl.PerMinuteLimit >= 0)
            {
                ctx.Response.Headers["X-RateLimit-Limit"]     = rl.PerMinuteLimit.ToString();
                ctx.Response.Headers["X-RateLimit-Remaining"] = Math.Max(0, rl.PerMinuteLimit - rl.PerMinuteUsed).ToString();
            }
            else
            {
                ctx.Response.Headers["X-RateLimit-Limit"]     = "unlimited";
                ctx.Response.Headers["X-RateLimit-Remaining"] = "unlimited";
            }

            ctx.Response.Headers["X-RateLimit-Reset"] = rl.ResetUnixTimestamp.ToString();

            if (rl.MonthlyLimit >= 0)
            {
                ctx.Response.Headers["X-Quota-Limit"]     = rl.MonthlyLimit.ToString();
                ctx.Response.Headers["X-Quota-Remaining"] = Math.Max(0, rl.MonthlyLimit - rl.MonthlyUsed).ToString();
            }
            else
            {
                ctx.Response.Headers["X-Quota-Limit"]     = "unlimited";
                ctx.Response.Headers["X-Quota-Remaining"] = "unlimited";
            }
        }

        private static ObjectResult JsonError(int status, string message, object? extra = null)
        {
            object body = extra == null
                ? new { error = message }
                : new { error = message, details = extra };

            return new ObjectResult(body) { StatusCode = status };
        }

        private static string GetIp(ActionExecutingContext ctx) =>
            ctx.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";

        private static string MaskKey(string key) =>
            key.Length > 8 ? key[..8] + "..." : "***";

        /// <summary>Logs a Warning when the same key hits 429 three times within 60 seconds.</summary>
        private static void TrackAbuse(string prefix, ILogger logger, string ip)
        {
            var queue = _abuseTracker.GetOrAdd(prefix, _ => new System.Collections.Generic.Queue<DateTime>());

            lock (queue)
            {
                var cutoff = DateTime.UtcNow.AddSeconds(-60);
                while (queue.Count > 0 && queue.Peek() < cutoff)
                    queue.Dequeue();

                queue.Enqueue(DateTime.UtcNow);

                if (queue.Count >= 3)
                {
                    logger.LogWarning(
                        "Potential abuse: API key prefix {Prefix} received {Count} rate-limit rejections in the last 60s. Client IP: {IP}",
                        prefix, queue.Count, ip);
                }
            }
        }
    }
}
