using Blazored.LocalStorage;
using Atomx.Common.Configuration;
using Atomx.Common.Utils;
using Atomx.Utils.Extension;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace Atomx.Admin.Client.Utils
{
    public class PersistentAuthenticationStateProvider : AuthenticationStateProvider
    {
        readonly ClaimsPrincipal anonymous = new(new ClaimsIdentity());

        static readonly Task<AuthenticationState> defaultUnauthenticatedTask = Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
        readonly Task<AuthenticationState> authenticationStateTask = defaultUnauthenticatedTask;

        readonly ILocalStorageService _localStorage;

        public PersistentAuthenticationStateProvider(PersistentComponentState state, ILocalStorageService localStorageService)
        {
            _localStorage = localStorageService;

            if (!state.TryTakeFromJson<UserInfo>(nameof(UserInfo), out var userInfo) || userInfo is null)
            {
                return;
            }

            var claims = new List<Claim>
                {
                    new(ClaimKeys.Id, userInfo.Id.ToString()),
                    new(ClaimKeys.Name, userInfo.Name),
                    new(ClaimKeys.Email, userInfo.Email),
                    new(ClaimKeys.Mobile, userInfo.MobilePhone),
                    new(ClaimKeys.Role, userInfo.Role),
                };
            foreach (var role in userInfo.Permissions)
            {
                claims.Add(new Claim(ClaimKeys.Permission, role));
            }

            authenticationStateTask = Task.FromResult(
                new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: nameof(PersistentAuthenticationStateProvider)))));
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            try
            {
                var jwtToken = await _localStorage.GetItemAsStringAsync(StorageKeys.JWTTokenKeyName);
                if (string.IsNullOrEmpty(jwtToken))
                    return await Task.FromResult(new AuthenticationState(anonymous));
                else
                {
                    var getUserClaims = DecryptToken(jwtToken);
                    if (getUserClaims == null)
                        return await Task.FromResult(new AuthenticationState(anonymous));
                    else
                    {
                        var claimsPrincipal = SetClaimPrincipal(getUserClaims);
                        return await Task.FromResult(new AuthenticationState(claimsPrincipal));
                    }
                }
            }
            catch
            {
                return await Task.FromResult(new AuthenticationState(anonymous));
            }
        }

        public ClaimsPrincipal SetClaimPrincipal(UserInfo customUserClaims)
        {
            if (string.IsNullOrEmpty(customUserClaims.Name))
                return new ClaimsPrincipal();
            else
            {
                var claims = new List<Claim>
                {
                    new(ClaimKeys.Id, customUserClaims.Id.ToString()),
                    new(ClaimKeys.Name, customUserClaims.Name),
                    new(ClaimKeys.Email, customUserClaims.Email),
                    new(ClaimKeys.Mobile, customUserClaims.MobilePhone),
                    new(ClaimKeys.Role, customUserClaims.Role.ToString()),
                };
                foreach (var role in customUserClaims.Permissions)
                {
                    claims.Add(new Claim(ClaimKeys.Permission, role));
                }
                return new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: nameof(PersistentAuthenticationStateProvider)));
            }

        }

        public void UpdateAuthenticationState(string jwtToken = "")
        {
            var claimsPrincipal = new ClaimsPrincipal();
            if (!string.IsNullOrEmpty(jwtToken))
            {
                var getUserClaims = DecryptToken(jwtToken);
                claimsPrincipal = SetClaimPrincipal(getUserClaims);
            }

            NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
        }

        private UserInfo DecryptToken(string jwtToken)
        {
            if (string.IsNullOrEmpty(jwtToken))
                return new UserInfo();
            else
            {
                var handler = new JwtSecurityTokenHandler();
                var token = handler.ReadJwtToken(jwtToken);



                var id = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Id)?.Value ?? string.Empty;
                var name = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Name)?.Value ?? string.Empty;
                var email = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Email)?.Value ?? string.Empty;
                var phone = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Mobile)?.Value ?? string.Empty;
                var role = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Role)?.Value ?? string.Empty;
                var permissions = token.Claims.Where(x => x.Type == ClaimKeys.Permission).Select(s => s.Value).ToArray();

                return new UserInfo(id.ToLong(), name, email, phone, role, permissions);
            }
        }

        public async Task MarkUserAsLoggedOut()
        {
            await _localStorage.RemoveItemAsync(StorageKeys.JWTTokenKeyName);
            await _localStorage.RemoveItemAsync(StorageKeys.RefreshTokenKeyName);

            var authState = Task.FromResult(new AuthenticationState(anonymous));

            NotifyAuthenticationStateChanged(authState);
        }
    }

    public record UserInfo(long Id = 0, string Name = null!, string Email = null!, string MobilePhone = null!, string Role = null!, string[] Permissions = null!);
}