namespace PowderCoating.Web.Middleware;

/// <summary>
/// Intercepts authenticated requests and redirects any user that has the
/// "MustChangePassword" claim to the change-password page before they can
/// access any other part of the application.
/// </summary>
public class MustChangePasswordMiddleware
{
    private readonly RequestDelegate _next;

    /// <summary>
    /// URL path prefixes that are reachable even before the mandatory password
    /// change is completed.  These are the minimum set of routes required to:
    /// <list type="bullet">
    ///   <item>Display and submit the change-password form itself.</item>
    ///   <item>Allow the user to log out if they do not want to proceed.</item>
    ///   <item>Load static assets served by the Identity UI (CSS, JS, etc.).</item>
    ///   <item>Serve logo and profile images referenced by the layout.</item>
    ///   <item>Allow SignalR hub negotiation and API calls that the page may
    ///         initiate before the redirect is completed client-side.</item>
    /// </list>
    /// All other paths redirect to <c>/Account/ChangeInitialPassword</c>.
    /// </summary>
    private static readonly string[] AllowedPrefixes =
    [
        "/Account/ChangeInitialPassword",
        "/Identity/Account/Logout",
        "/Identity/",
        "/CompanyLogo",
        "/Profile/Photo",
        "/api/",
        "/stripe/",
        "/hubs/",
    ];

    /// <summary>
    /// Initialises the middleware with the next request delegate in the pipeline.
    /// </summary>
    /// <param name="next">The next middleware component.</param>
    public MustChangePasswordMiddleware(RequestDelegate next) => _next = next;

    /// <summary>
    /// Intercepts every request.  If the authenticated user carries the
    /// <c>MustChangePassword=true</c> claim and is requesting a path not in
    /// <see cref="AllowedPrefixes"/>, issues a redirect to the change-password
    /// page and short-circuits the rest of the pipeline.
    /// <para>
    /// The claim is set by the admin when creating or resetting a user account.
    /// Using a claim (rather than a DB flag) means the check is evaluated on
    /// every request without an additional database query, at the cost of a
    /// sign-out being required to clear it — which is acceptable since the
    /// password change form re-signs the user in automatically.
    /// </para>
    /// </summary>
    /// <param name="context">The current HTTP context.</param>
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.User.Identity?.IsAuthenticated == true &&
            context.User.FindFirst("MustChangePassword")?.Value == "true")
        {
            var path = context.Request.Path.Value ?? string.Empty;
            var allowed = AllowedPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase));

            if (!allowed)
            {
                context.Response.Redirect("/Account/ChangeInitialPassword");
                return;
            }
        }

        await _next(context);
    }
}
