using System.Security.Claims;
using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Infra.Persistence;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Infra.Auth;

/// <summary>
/// Maps an authenticated AuthHex principal to ERPCore's local identity (docs/10 A.4/A.5).
/// AuthHex tokens carry the user as a custom <c>UserId</c> (GUID) claim and no
/// <c>sub</c>/<c>nameid</c>. This transformation JIT-provisions a local shadow
/// <see cref="User"/> (keyed by <c>auth_user_id</c>) and injects the local
/// <c>int</c> id as <see cref="ClaimTypes.NameIdentifier"/>, so
/// <see cref="ICurrentUser"/>/<c>AuditUserId</c> resolve the real user unchanged.
/// Idempotent — <see cref="IClaimsTransformation"/> may run several times per request.
/// </summary>
public sealed class ShadowUserClaimsTransformation : IClaimsTransformation
{
    private readonly ErpDbContext _db;

    public ShadowUserClaimsTransformation(ErpDbContext db) => _db = db;

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is not ClaimsIdentity identity || !identity.IsAuthenticated)
            return principal;
        if (identity.HasClaim(c => c.Type == ClaimTypes.NameIdentifier))
            return principal; // already resolved this request

        var raw = principal.FindFirstValue(AuthHexClaims.UserId);
        if (!Guid.TryParse(raw, out var authUserId))
            return principal; // no mappable identity → CurrentUser falls back to system

        var nic = principal.FindFirstValue(AuthHexClaims.Nic);
        var localId = await ResolveOrProvisionAsync(authUserId, nic);

        identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, localId.ToString()));
        return principal;
    }

    private async Task<int> ResolveOrProvisionAsync(Guid authUserId, string? nic)
    {
        var existing = await _db.Users.AsNoTracking()
            .Where(u => u.AuthUserId == authUserId)
            .Select(u => u.UserId)
            .FirstOrDefaultAsync();
        if (existing != 0) return existing;

        var label = string.IsNullOrWhiteSpace(nic) ? authUserId.ToString() : nic.Trim();
        var user = new User
        {
            AuthUserId = authUserId,
            Username = label,
            DisplayName = string.IsNullOrWhiteSpace(nic) ? "AuthHex User" : nic.Trim(),
            Status = EntityStatus.Active
        };

        try
        {
            _db.Users.Add(user);
            await _db.SaveChangesAsync();
            return user.UserId;
        }
        catch (DbUpdateException)
        {
            // Lost a race (unique auth_user_id) — the row now exists; re-read it.
            _db.Entry(user).State = EntityState.Detached;
            return await _db.Users.AsNoTracking()
                .Where(u => u.AuthUserId == authUserId)
                .Select(u => u.UserId)
                .FirstAsync();
        }
    }
}
