using Microsoft.EntityFrameworkCore; using eprintServer.Data; using eprintServer.Entities; using eprintServer.Enums; using eprintServer.Responses; using eprintServer.Requests; using eprintServer.Interfaces; using Newtonsoft.Json; using System.Security.Claims; using System.Text; using System.Text.RegularExpressions; namespace eprintServer.Services; public class UserService { private readonly DataContext _context; private readonly IFileStorageService _fileStorageService; public UserService(DataContext context, IFileStorageService fileStorageService) { _context = context; _fileStorageService = fileStorageService; } public async Task SaveAdminAsync(User user) { if (!string.IsNullOrEmpty(user.Password)) { user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password); } user.Role = RoleEnum.ROLE_ADMIN; user.Active = true; _context.Users.Add(user); await _context.SaveChangesAsync(); return user; } public async Task ProcessFileAsync(IFormFile file, string subDirectory) { if (file == null || file.Length == 0) return null; var result = await _fileStorageService.SaveFileAsync(file, subDirectory); return result; } public async Task FindByLoginIdentifierAsync(string loginIdentifier) { var identifierLower = loginIdentifier.ToLower(); return await _context.Users .Include(u => u.Device) .FirstOrDefaultAsync(u => u.Username.ToLower() == identifierLower || u.Email.ToLower() == identifierLower); } public async Task GetCurrentUserAsync(ClaimsPrincipal user) { var userIdClaim = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out int userId)) { return null; } return await FindByIdAsync(userId); } public async Task> GetAdminUserIdsAsync() { return await _context.Users .Where(u => u.Role == RoleEnum.ROLE_ADMIN && u.Active && !u.Deleted) .Select(u => u.Id) .ToListAsync(); } public async Task FindByUsernameAsync(string username) { return await _context.Users .Include(u => u.Device) .FirstOrDefaultAsync(u => u.Username.ToLower() == username.ToLower()); } public string GetUserNameFromToken(string token) { try { var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); var jwtToken = handler.ReadJwtToken(token); // Try to get 'sub' (Subject) or 'email' var userName = jwtToken.Subject ?? jwtToken.Claims.FirstOrDefault(c => c.Type == "email")?.Value; return userName; } catch (Exception) { // Fallback: Manual split if the handler fails (like your Java code) try { var parts = token.Split('.'); if (parts.Length < 2) return null; var payload = parts[1]; var decodedBytes = Convert.FromBase64String(pad(payload)); var payloadJson = System.Text.Encoding.UTF8.GetString(decodedBytes); dynamic data = JsonConvert.DeserializeObject(payloadJson); return data?.email ?? data?.sub; } catch { return null; } } } // Helper to fix Base64 padding if needed private string pad(string s) => s.PadRight(s.Length + (4 - s.Length % 4) % 4, '='); public async Task SaveUserAsync(User user) { if (!string.IsNullOrEmpty(user.Password)) { user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password); } user.Role = RoleEnum.ROLE_USER; _context.Users.Add(user); await _context.SaveChangesAsync(); return user; } public async Task SaveSubAdminAsync(User user) { if (!string.IsNullOrEmpty(user.Password)) { user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password); } user.Role = RoleEnum.ROLE_SUB_ADMIN; _context.Users.Add(user); await _context.SaveChangesAsync(); return user; } public async Task UpdateUserPassAsync(User user) { if (!string.IsNullOrEmpty(user.Password)) { user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password); } await _context.SaveChangesAsync(); return user; } public async Task UpdateUserRoleAsync(User user, RoleEnum role) { user.Role = role; _context.Users.Update(user); await _context.SaveChangesAsync(); return user; } public async Task UpdateUserAsync(User user) { _context.Users.Update(user); await _context.SaveChangesAsync(); return user; } public async Task DoesUsernameExistAsync(string username) { return await _context.Users.AnyAsync(u => u.Username.ToLower() == username.ToLower()); } public async Task FindByIdAsync(long userId) { return await _context.Users.FindAsync(userId); } public async Task UpdatePasswordAsync(User user, string newPassword) { user.Password = BCrypt.Net.BCrypt.HashPassword(newPassword); _context.Users.Update(user); await _context.SaveChangesAsync(); } public async Task ExistByEmailAsync(string email) { return await _context.Users.AnyAsync(u => u.Email.ToLower() == email.ToLower()); } public async Task FindByEmailAsync(string email) { if (string.IsNullOrWhiteSpace(email)) return null; var e = email.ToLower(); return await _context.Users .Include(u => u.Device) .FirstOrDefaultAsync(u => u.Email.ToLower() == e); } public async Task ExistByPhoneNumberAsync(string? phoneNumber) { // 1. Handle null or empty phone number if (string.IsNullOrWhiteSpace(phoneNumber)) { return false; } // 2. Check if any user exists with the provided phone number return await _context.Users .AnyAsync(u => u.PhoneNumber == phoneNumber); } public async Task GetUserCountAsync() { return await _context.Users.CountAsync(); } public string GenerateUsername(string name, long userCount) { const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var random = new Random(); var randomPart = new StringBuilder(4); for (int i = 0; i < 4; i++) { randomPart.Append(chars[random.Next(chars.Length)]); } string baseName = "USER"; if (!string.IsNullOrWhiteSpace(name)) { baseName = Regex.Replace(name, @"\s+", "").ToUpper(); } if (string.IsNullOrEmpty(baseName)) { baseName = "USER"; } string username = $"{baseName}{userCount}{randomPart}"; username = Regex.Replace(username, "[^A-Za-z0-9]", ""); return username; } public async Task> FindUsersByCriteriaAsync( int page, int size, string? search = null, RoleEnum? role = null, string? categoryName = null, bool? active = null, bool? status = null, bool? deleted = null, bool? freeEvaluator = null, bool? freeInspector = null) { var query = _context.Users.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) { var searchLower = search.ToLower(); query = query.Where(u => u.Name.ToLower().Contains(searchLower) || u.Email.ToLower().Contains(searchLower)); } if (role.HasValue) { query = query.Where(u => u.Role == role.Value); } if (active.HasValue) { query = query.Where(u => u.Active == active.Value); } if (deleted.HasValue) { query = query.Where(u => u.Deleted == deleted.Value); } var totalCount = await query.CountAsync(); query = query.OrderByDescending(u => u.Timestamp); var items = await query .Skip(page * size) .Take(size) .ToListAsync(); return new PagedResult(items, totalCount, page, size); } public async Task> FindUserByCriteriaList( string? search = null, RoleEnum? role = null, string? categoryName = null, bool? active = null, bool? status = null, bool? deleted = null, bool? freeEvaluator = null, bool? freeInspector = null) { var query = _context.Users.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) { var searchLower = search.ToLower(); query = query.Where(u => u.Name.ToLower().Contains(searchLower) || u.Email.ToLower().Contains(searchLower)); } if (role.HasValue) { query = query.Where(u => u.Role == role.Value); } if (active.HasValue) { query = query.Where(u => u.Active == active.Value); } if (deleted.HasValue) { query = query.Where(u => u.Deleted == deleted.Value); } query = query.OrderByDescending(u => u.Timestamp); var items = await query.ToListAsync(); return items; } public async Task UpdateUserProfileAsync(long userId, UpdateProfileRequest request) { var user = await _context.Users .FirstOrDefaultAsync(u => u.Id == userId); if (user == null) return null; if (request.Name != null) user.Name = request.Name; if (request.Gendre != null) user.Gendre = request.Gendre; if (request.Email != null) user.Email = request.Email; if (request.PhoneNumber != null) user.PhoneNumber = request.PhoneNumber; if (request.City != null) user.City = request.City; if (request.Country != null) user.Country = request.Country; if (request.OfficeRole != null) user.OfficeRole = request.OfficeRole; if (request.Active.HasValue) user.Active = request.Active.Value; if (request.Deleted.HasValue) user.Deleted = request.Deleted.Value; await _context.SaveChangesAsync(); return user; } public async Task UpdateUserPasswordAsync(long userId, UpdatePasswordRequest request) { var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return null; } if (string.IsNullOrEmpty(user.Password) || !BCrypt.Net.BCrypt.Verify(request.OldPassword, user.Password)) { throw new InvalidOperationException("Old password is not correct."); } if (!string.IsNullOrEmpty(request.NewPassword)) { user.Password = BCrypt.Net.BCrypt.HashPassword(request.NewPassword); } await _context.SaveChangesAsync(); return user; } public async Task UpdateSubAdminPrivilegesAsync(long userId, List newPrivileges) { var userToUpdate = await _context.Users .FirstOrDefaultAsync(u => u.Id == userId); if (userToUpdate == null) { return null; } userToUpdate.SubAdminsPrivilegesList = newPrivileges ?? new List(); await _context.SaveChangesAsync(); return userToUpdate; } public async Task> GetPagedUsersByCriteriaAsync( int page, int size, RoleEnum? role = null, string? search = null) { var query = _context.Users.AsQueryable() .Where(u => u.Deleted == false); if (role.HasValue) { query = query.Where(u => u.Role == role.Value); } if (!string.IsNullOrEmpty(search)) { var lowerSearch = search.ToLower(); query = query.Where(u => u.Email.ToLower().Contains(lowerSearch) || u.Name.ToLower().Contains(lowerSearch)); } var totalCount = await query.CountAsync(); var items = await query .OrderBy(u => u.Id) .Skip(page * size) .Take(size) .ToListAsync(); return new PagedResult(items, totalCount, page, size); } public async Task ToggleUserActiveStatusAsync(long id) { var user = await _context.Users.FindAsync(id); if (user == null) return null; // Toggle the active state user.Active = !user.Active; _context.Users.Update(user); await _context.SaveChangesAsync(); return user; } public async Task<(User? User, string? Error)> UpdateUserBasicInfoAsync(long id, UpdateUserBasicInfoRequest request) { var user = await _context.Users.FindAsync(id); if (user == null) return (null, "User not found"); // 1. Validate Email Uniqueness (only if provided and different) if (request.Email != null && !string.Equals(user.Email, request.Email, StringComparison.OrdinalIgnoreCase)) { bool emailExists = await _context.Users.AnyAsync(u => u.Email == request.Email && u.Id != id); if (emailExists) return (null, "EMAIL_EXISTS"); user.Email = request.Email; } // 2. Validate Phone Number Uniqueness (only if provided and different) if (request.PhoneNumber != null && user.PhoneNumber != request.PhoneNumber) { bool phoneExists = await _context.Users.AnyAsync(u => u.PhoneNumber == request.PhoneNumber && u.Id != id); if (phoneExists) return (null, "PHONE_EXISTS"); user.PhoneNumber = request.PhoneNumber; } // 3. Update remaining attributes only if they are not null if (request.Name != null) user.Name = request.Name; if (request.Gendre.HasValue) user.Gendre = request.Gendre.Value; if (request.City != null) user.City = request.City; if (request.Country != null) user.Country = request.Country; _context.Users.Update(user); await _context.SaveChangesAsync(); return (user, null); } }