using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Marechai.Areas.Identity
{
    public class RevalidatingIdentityAuthenticationStateProvider<TUser> : RevalidatingServerAuthenticationStateProvider
        where TUser : class
    {
        readonly IdentityOptions      _options;
        readonly IServiceScopeFactory _scopeFactory;

        public RevalidatingIdentityAuthenticationStateProvider(ILoggerFactory loggerFactory,
                                                               IServiceScopeFactory scopeFactory,
                                                               IOptions<IdentityOptions> optionsAccessor) :
            base(loggerFactory)
        {
            _scopeFactory = scopeFactory;
            _options      = optionsAccessor.Value;
        }

        protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);

        protected override async Task<bool> ValidateAuthenticationStateAsync(
            AuthenticationState authenticationState, CancellationToken cancellationToken)
        {
            // Get the user manager from a new scope to ensure it fetches fresh data
            IServiceScope scope = _scopeFactory.CreateScope();

            try
            {
                UserManager<TUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();

                return await ValidateSecurityStampAsync(userManager, authenticationState.User);
            }
            finally
            {
                if(scope is IAsyncDisposable asyncDisposable)
                {
                    await asyncDisposable.DisposeAsync();
                }
                else
                {
                    scope.Dispose();
                }
            }
        }

        async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
        {
            TUser user = await userManager.GetUserAsync(principal);

            if(user == null)
            {
                return false;
            }

            if(!userManager.SupportsUserSecurityStamp)
            {
                return true;
            }

            string principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
            string userStamp      = await userManager.GetSecurityStampAsync(user);

            return principalStamp == userStamp;
        }
    }
}