using HikvisionAttendanceManager.App.Models; namespace HikvisionAttendanceManager.App.Services; public sealed class UserManagementService( HikvisionIsapiClient hikvision, EmployeePhotoService photos, AttendanceMachineUserRepository machineUsers, OperationHistoryService history) { public static EmployeeSyncResult ValidateEmployee(HrmsEmployee employee, long siteId) { var result = new EmployeeSyncResult { Employee = employee, EmployeeNumber = employee.SerialNumber, EmployeeName = employee.Name, Department = employee.DepartmentName, OverallResult = "READY" }; if (!employee.Active) { result.OverallResult = "SKIPPED"; result.Reason = "Employee inactive."; } else if (employee.DepartmentId is null) { result.OverallResult = "SKIPPED"; result.Reason = "Department not found."; } else if (!employee.DepartmentActive) { result.OverallResult = "SKIPPED"; result.Reason = "Department inactive."; } else if (employee.LocationSiteId != siteId) { result.OverallResult = "SKIPPED"; result.Reason = "Wrong location site."; } return result; } public async Task CreateUserWithFaceAsync(Device device, HrmsEmployee employee, long siteId, IProgress? progress, CancellationToken cancellationToken) { var validation = ValidateEmployee(employee, siteId); if (validation.OverallResult == "SKIPPED") return validation; var entry = new SyncHistoryEntry { Operation = SyncOperationKind.UserCreation, TargetDevice = device.DisplayName, Context = employee.SerialNumber + " — " + employee.Name, Total = 1, Status = "Running" }; await history.AddAsync(entry, cancellationToken); var result = new EmployeeSyncResult { Employee = employee, EmployeeNumber = employee.SerialNumber, EmployeeName = employee.Name, Department = employee.DepartmentName }; try { progress?.Report("Checking if user already exists…"); if (await hikvision.UserExistsAsync(device, employee.SerialNumber, cancellationToken)) { result.UserStatus = "Already exists"; result.OverallResult = "ALREADY_EXISTS"; } else { progress?.Report("Creating Hikvision UserInfo…"); var create = await hikvision.CreateUserAsync(device, employee, cancellationToken); if (!create.Success) { result.UserStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = create.Reason; await CompleteHistory(entry, result, cancellationToken); return result; } result.UserStatus = "Created"; } progress?.Report("Downloading employee photo…"); var photo = await photos.DownloadAndNormalizeAsync(employee, cancellationToken); if (!photo.Success) { result.FaceStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = photo.Reason; await CompleteHistory(entry, result, cancellationToken); return result; } progress?.Report("Uploading face…"); var face = await hikvision.UploadFaceAsync(device, employee.SerialNumber, photo.Bytes, cancellationToken); if (!face.Success) { result.FaceStatus = "Failed"; result.OverallResult = "FAILED"; result.Reason = face.Reason; await CompleteHistory(entry, result, cancellationToken); return result; } result.FaceStatus = "Uploaded"; progress?.Report("Verifying face enrollment…"); var verify = await hikvision.VerifyFaceAsync(device, employee.SerialNumber, cancellationToken); result.VerificationStatus = verify.Success ? "Verified" : "Failed"; result.OverallResult = verify.Success ? "SUCCESS" : "FAILED"; result.Reason = verify.Reason; if (verify.Success) await machineUsers.UpsertAsync(device.MachineId, employee.SerialNumber, employee.Name, cancellationToken); await CompleteHistory(entry, result, cancellationToken); return result; } catch (Exception ex) { result.OverallResult = "FAILED"; result.Reason = ex is InvalidOperationException ? ex.Message : "Device unavailable or operation failed."; await CompleteHistory(entry, result, cancellationToken); return result; } } public async Task> DeleteUsersAsync(Device device, IReadOnlyList employeeNumbers, IProgress? progress, CancellationToken cancellationToken) { var normalized = employeeNumbers.Where(e => !string.IsNullOrWhiteSpace(e)).Select(e => e.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var entry = new SyncHistoryEntry { Operation = SyncOperationKind.UserDeletion, TargetDevice = device.DisplayName, Context = normalized.Count == 1 ? normalized[0] : $"{normalized.Count} employees", Total = normalized.Count, Status = "Running" }; await history.AddAsync(entry, cancellationToken); var results = new List(); if (normalized.Count == 0) { entry.Status = "Completed"; entry.CompletedAt = DateTime.Now; await history.UpdateAsync(entry, cancellationToken); return results; } progress?.Report("Deleting users on device…"); var batchResult = await hikvision.DeleteUsersAsync(device, normalized, cancellationToken); foreach (var employeeNo in normalized) { if (batchResult.Success) { await machineUsers.MarkDeletedAsync(device.MachineId, employeeNo, cancellationToken); results.Add(new EmployeeSyncResult { EmployeeNumber = employeeNo, UserStatus = "Deleted", OverallResult = "SUCCESS" }); } else { results.Add(new EmployeeSyncResult { EmployeeNumber = employeeNo, UserStatus = "Failed", OverallResult = "FAILED", Reason = batchResult.Reason }); } } entry.CompletedAt = DateTime.Now; entry.Success = results.Count(r => r.OverallResult == "SUCCESS"); entry.Failed = results.Count(r => r.OverallResult == "FAILED"); entry.Status = entry.Failed > 0 ? "Completed with errors" : "Completed"; await history.UpdateAsync(entry, cancellationToken); return results; } private async Task CompleteHistory(SyncHistoryEntry entry, EmployeeSyncResult result, CancellationToken cancellationToken) { entry.CompletedAt = DateTime.Now; entry.Success = result.OverallResult is "SUCCESS" or "ALREADY_EXISTS" ? 1 : 0; entry.Failed = result.OverallResult == "FAILED" ? 1 : 0; entry.Skipped = result.OverallResult == "SKIPPED" ? 1 : 0; entry.Status = result.OverallResult == "FAILED" ? "Failed" : "Completed"; await history.UpdateAsync(entry, cancellationToken); } }