context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using System.Security.Claims;
  using System.Security.Cryptography;
  using System.Threading;
  using System.Threading.Tasks;
  using Machete.Data.Infrastructure;
  using Microsoft.AspNetCore.Identity;
  using Microsoft.EntityFrameworkCore;
  
  namespace Machete.Data.Identity
  {
      /// <summary>
      /// This is basically a data access repository for the Identity User Store. We are overriding the default UserStore
      /// because Identity does not support multitenancy out of the box.
      /// </summary>
      public class MacheteUserStore : IUserStore<MacheteUser>, 
                                      IUserClaimStore<MacheteUser>, // nec. for JWT
                                      IUserLoginStore<MacheteUser>,
                                      IUserRoleStore<MacheteUser>,
                                      IUserPasswordStore<MacheteUser>,
                                    //IUserTwoFactorStore<MacheteUser>,
                                      IUserEmailStore<MacheteUser>//,
                                    //IUserLockoutStore<MacheteUser>,
                                    //IQueryableUserStore<MacheteUser>
      {
          private MacheteContext _dataContext;
          private DbSet<MacheteUser> _users; // IDbSet (Framework)
          private DbSet<MacheteRole> _roles;
          private DbSet<MacheteUserRole> _userRoles;
          private DbSet<MacheteUserClaim> _userClaims;
  
          public MacheteUserStore(IDatabaseFactory factory)
          {
              _dataContext = factory.Get(); // should have its own _tenantService
              _users = _dataContext.Users;
              _roles = _dataContext.Roles;
              _userRoles = _dataContext.UserRoles;
              _userClaims = _dataContext.UserClaims;
          }
  
          public MacheteUserStore(MacheteContext assignedContext, bool isSeedInstance)
          {
              _dataContext = assignedContext;
              _users = _dataContext.Users;
              _roles = _dataContext.Roles;
              _userRoles = _dataContext.UserRoles;
          }
  
          public Task<string> GetUserIdAsync(MacheteUser user,
                                             CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              return Task.FromResult(user.Id.ToString());
          }
  
          public Task<string> GetUserNameAsync(MacheteUser user,
                                               CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              return Task.FromResult(user.UserName);
          }
  
          public Task SetUserNameAsync(MacheteUser user, string userName,
                                       CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              user.UserName = userName;
              //user.NormalizedUserName = userName.ToUpper();
              
              _users.Update(user);
  
              _dataContext.SaveChanges();
              
              return Task.FromResult(user.UserName);
          }
  
          public Task<string> GetNormalizedUserNameAsync(MacheteUser user,
                                                         CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              return Task.FromResult(user.NormalizedUserName);
          }
  
          public Task SetNormalizedUserNameAsync(MacheteUser user, string normalizedName,
                                                 CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              user.NormalizedUserName = normalizedName;
  
              _users.Update(user);
  
              //_dataContext.SaveChanges();
              
              return Task.FromResult(user.NormalizedUserName);
          }
  
          public Task<IdentityResult> CreateAsync(MacheteUser user,
                                                  CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user)); // new up your own; follows pattern?
  
              var unused = _users.Add(user);
  
              _dataContext.SaveChanges();
  
              return Task.FromResult(IdentityResult.Success);
          }
  
          public Task<IdentityResult> UpdateAsync(MacheteUser user,
                                                  CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              var unused = _users.Update(user);
  
              return Task.FromResult(IdentityResult.Success);
          }
  
          public Task<IdentityResult> DeleteAsync(MacheteUser user,
                                                  CancellationToken cancellationToken = default(CancellationToken))
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              var unused = _users.Remove(user);
              
              return Task.FromResult(IdentityResult.Success);
          }
  
          public Task<MacheteUser> FindByIdAsync(string userId,
                                                 CancellationToken cancellationToken = default(CancellationToken))
          {
              var user = _users.FirstOrDefault(x => x.Id == userId);           
              //if (user == null) throw new NullReferenceException("User not found.");
              
              return Task.FromResult(user);
          }
  
          public Task<MacheteUser> FindByNameAsync(string normalizedUserName,
                                                   CancellationToken cancellationToken = default(CancellationToken))
          {
              var macheteUsers = _users.Where(u => u.NormalizedUserName == normalizedUserName);
              if (macheteUsers.Count() > 1) throw new Exception("Attempted to find unique username, but found multiple.");
              
              return Task.FromResult(macheteUsers.FirstOrDefault());
          }
          
          public Task SetPasswordHashAsync(MacheteUser user, string passwordHash, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
              //if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash));
  
              user.PasswordHash = passwordHash;
  
              _users.Update(user);
  
              //_dataContext.SaveChanges();
              
              return Task.FromResult<object>(null);
          }
  
          public Task<string> GetPasswordHashAsync(MacheteUser user, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              return Task.FromResult(user.PasswordHash);
          }
  
          public Task<bool> HasPasswordAsync(MacheteUser user, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              return String.IsNullOrEmpty(user.PasswordHash) ? Task.FromResult(false) : Task.FromResult(true);
          }
          
          public Task AddLoginAsync(MacheteUser user, UserLoginInfo login, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              _users.Add(user);
  
              _dataContext.SaveChanges();
  
              return Task.CompletedTask;
          }
  
          public Task RemoveLoginAsync(MacheteUser user, string loginProvider, string providerKey, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
              _users.Remove(user);
  
              _dataContext.SaveChanges();
              
              return Task.CompletedTask;
          }
  
          public Task<IList<UserLoginInfo>> GetLoginsAsync(MacheteUser user, CancellationToken cancellationToken)
          {
              cancellationToken.ThrowIfCancellationRequested();
              if (user == null) throw new ArgumentNullException(nameof(user));
  
  
gt:             return Task.FromResult((IList<UserLoginInfo>) user.Logins);