using Hcs.WebApp.Data.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace Hcs.WebApp.Services
{
    public class UsersService(
        IDbContextFactory<AppIdentityDbContext> factory,
        UserManager<AppUser> userManager,
        IPasswordHasher<AppUser> passwordHasher)
    {
        private readonly IDbContextFactory<AppIdentityDbContext> factory = factory;
        private readonly UserManager<AppUser> userManager = userManager;
        private readonly IPasswordHasher<AppUser> passwordHasher = passwordHasher;

        public async Task<IEnumerable<AppUserWithRole>> GetUsersWithRoleAsync()
        {
            using var context = factory.CreateDbContext();
            return await (from user in context.Users
                          join userRole in context.UserRoles on user.Id equals userRole.UserId
                          join role in context.Roles on userRole.RoleId equals role.Id
                          orderby role.Priority
                          select new AppUserWithRole()
                          {
                              User = user,
                              Role = role
                          }).ToListAsync();
        }

        public async Task<IdentityResult> CreateUserAsync(string userName, string roleName, string password)
        {
            var user = new AppUser()
            {
                UserName = userName,
                NormalizedUserName = userName.Normalize()
            };
            var result = await userManager.CreateAsync(user, password);
            if (result.Succeeded)
            {
                result = await userManager.AddToRolesAsync(user, [roleName]);
            }
            return result;
        }

        public async Task UpdateUserAsync(string userId, string roleId, string password)
        {
            using var context = factory.CreateDbContext();
            var executionStrategy = context.Database.CreateExecutionStrategy();
            await executionStrategy.ExecuteAsync(async () =>
            {
                using var transaction = await context.Database.BeginTransactionAsync();
                try
                {
                    var curUserRole = await (from userRole in context.UserRoles
                                             where userRole.UserId == userId
                                             select userRole).SingleAsync();
                    var curRole = await (from role in context.Roles
                                         where role.Id == curUserRole.RoleId
                                         select role).SingleAsync();
                    if (curRole.Id != roleId)
                    {
                        context.UserRoles.Remove(curUserRole);

                        var newRole = await (from role in context.Roles
                                             where role.Id == roleId
                                             select role).SingleAsync();
                        await context.UserRoles.AddAsync(new IdentityUserRole<string>()
                        {
                            UserId = userId,
                            RoleId = newRole.Id
                        });
                    }

                    var curUser = await (from user in context.Users
                                         where user.Id == userId
                                         select user).SingleAsync();
                    var newPasswordHash = passwordHasher.HashPassword(curUser, password);
                    if (curUser.PasswordHash != newPasswordHash)
                    {
                        curUser.PasswordHash = newPasswordHash;

                        context.Users.Update(curUser);
                    }

                    await context.SaveChangesAsync();

                    await transaction.CommitAsync();
                }
                catch
                {
                    await transaction.RollbackAsync();

                    throw;
                }
            });
        }

        public async Task<IdentityResult> DeleteUserAsync(string userId)
        {
            var user = await userManager.FindByIdAsync(userId);
            return await userManager.DeleteAsync(user);
        }
    }
}
