using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Memory;
using PowderCoating.Core.Entities;

namespace PowderCoating.Web.Middleware;

/// <summary>
/// Ensures <see cref="ApplicationUser.LastLoginDate"/> is always stamped for every authenticated
/// user, regardless of which sign-in path they used (password, 2FA, registration auto-sign-in,
/// external providers, etc.).
///
/// The explicit stamps in <c>Login.cshtml.cs</c> and <c>RegistrationController</c> cover the most
/// common paths; this middleware is the belt-and-suspenders layer that catches everything else —
/// in particular the default Identity <c>LoginWith2fa</c> page, any future sign-in routes, and
/// any user whose <c>LastLoginDate</c> is null because they signed in before the field existed.
///
/// To avoid a DB write on every request, an <see cref="IMemoryCache"/> entry keyed by user ID
/// is set for one hour after a successful stamp. The first authenticated request in each hour
/// window costs at most one DB read (to check if the field is null) plus one write (if it is).
/// Subsequent requests within the hour are handled entirely from cache.
/// </summary>
public class LoginTrackingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;

    public LoginTrackingMiddleware(RequestDelegate next, IMemoryCache cache)
    {
        _next = next;
        _cache = cache;
    }

    /// <summary>
    /// Stamps <see cref="ApplicationUser.LastLoginDate"/> for the current authenticated user if
    /// it has not been recorded yet, then delegates to the rest of the pipeline.
    /// The <paramref name="userManager"/> is resolved per-request (scoped lifetime) via the
    /// <c>InvokeAsync</c> parameter rather than constructor injection to avoid a captive-dependency
    /// problem (middleware instances are singletons but UserManager is scoped).
    /// </summary>
    public async Task InvokeAsync(HttpContext context, UserManager<ApplicationUser> userManager)
    {
        if (context.User.Identity?.IsAuthenticated == true)
        {
            var userId = userManager.GetUserId(context.User);
            if (userId != null)
            {
                var cacheKey = $"loginstamp:{userId}";
                if (!_cache.TryGetValue(cacheKey, out _))
                {
                    var user = await userManager.FindByIdAsync(userId);
                    if (user != null && user.LastLoginDate == null)
                    {
                        user.LastLoginDate = DateTime.UtcNow;
                        await userManager.UpdateAsync(user);
                    }

                    // Cache for 1 hour regardless of whether we wrote — avoids repeated DB reads
                    // for users who already have a date set.
                    _cache.Set(cacheKey, true, TimeSpan.FromHours(1));
                }
            }
        }

        await _next(context);
    }
}
