using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using PowderCoating.Core.Entities;
using System.Security.Claims;

namespace PowderCoating.Infrastructure.Identity;

/// <summary>
/// Custom ASP.NET Core Identity claims factory that embeds application-specific claims into the
/// user's <see cref="ClaimsPrincipal"/> at login time.
/// <para>
/// Registered in <c>Program.cs</c> via
/// <c>builder.Services.AddScoped&lt;IUserClaimsPrincipalFactory&lt;ApplicationUser&gt;, CustomUserClaimsPrincipalFactory&gt;()</c>
/// so that ASP.NET Core Identity calls it instead of the default factory whenever a cookie is issued.
/// </para>
/// <para>
/// <b>Why embed claims at login?</b>  Reading <c>CompanyId</c> or <c>CompanyRole</c> from the database
/// on every request would add a synchronous DB round-trip to the authentication middleware, which runs
/// on every single HTTP request.  Embedding the values as signed cookie claims at login time means
/// subsequent requests are verified cryptographically with zero extra DB queries.
/// </para>
/// <para>
/// Claims added:
/// <list type="bullet">
///   <item><description><c>"CompanyId"</c> — the integer ID of the company the user belongs to. Read by <see cref="Data.ApplicationDbContext.CurrentCompanyId"/> to enforce multi-tenancy query filters.</description></item>
///   <item><description><c>"CompanyRole"</c> — the user's role within their company (e.g., CompanyAdmin, Manager, Worker). Distinct from the ASP.NET Identity system role (SuperAdmin).</description></item>
/// </list>
/// </para>
/// </summary>
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    /// <summary>
    /// Initialises the factory with the Identity infrastructure services required by the base class.
    /// </summary>
    /// <param name="userManager">Used by the base class to load user-level claims.</param>
    /// <param name="roleManager">Used by the base class to load role-level claims.</param>
    /// <param name="options">Identity options, including the claim-type mappings used by the base class.</param>
    public CustomUserClaimsPrincipalFactory(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager,
        IOptions<IdentityOptions> options)
        : base(userManager, roleManager, options)
    {
    }

    /// <summary>
    /// Generates the <see cref="ClaimsIdentity"/> for <paramref name="user"/> by first calling the base
    /// implementation (which adds standard Identity claims such as NameIdentifier, Email, and Role) and
    /// then appending the application-specific <c>CompanyId</c> and <c>CompanyRole</c> claims.
    /// <para>
    /// Each claim addition is wrapped in an individual try/catch to ensure that a missing or malformed
    /// value on one field does not fail the entire authentication flow — the user is logged in with
    /// whatever valid claims could be assembled.  Errors are written to the debug output rather than
    /// thrown so that login remains functional even in degraded configurations.
    /// </para>
    /// </summary>
    /// <param name="user">The authenticated <see cref="ApplicationUser"/> whose claims are being constructed.</param>
    /// <returns>A <see cref="ClaimsIdentity"/> containing all standard and application-specific claims.</returns>
    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);

        // Add CompanyId claim - only if valid
        if (user.CompanyId > 0)
        {
            try
            {
                identity.AddClaim(new Claim("CompanyId", user.CompanyId.ToString()));
            }
            catch (Exception ex)
            {
                // Log but don't fail authentication
                System.Diagnostics.Debug.WriteLine($"Error adding CompanyId claim: {ex.Message}");
            }
        }

        // Add CompanyRole claim - only if valid and not empty
        if (!string.IsNullOrWhiteSpace(user.CompanyRole))
        {
            try
            {
                // Sanitize the role value to ensure it's safe
                var sanitizedRole = user.CompanyRole.Trim();
                identity.AddClaim(new Claim("CompanyRole", sanitizedRole));
            }
            catch (Exception ex)
            {
                // Log but don't fail authentication
                System.Diagnostics.Debug.WriteLine($"Error adding CompanyRole claim: {ex.Message}");
            }
        }

        return identity;
    }
}
