import Foundation /// Internal struct to persist user credentials locally. private struct LocalUserRecord: Codable { var user: UserAccount var passwordHash: String } /// Concrete local implementation of `AuthServiceProtocol`. /// Handles registration, authentication, profile updates, and password changes purely in local storage (UserDefaults / sandbox). final class LocalAuthRepository: AuthServiceProtocol { private let usersKey = "com.celestia.trace.registered_users_db" private let currentUserKey = "com.celestia.trace.current_user_session" init() {} // MARK: - AuthServiceProtocol func login(request: LoginRequest, completion: @escaping (AuthResult) -> Void) { let identifier = request.identifier.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() let password = request.password guard !identifier.isEmpty, password.count >= 6 else { completion(.failure(.invalidInput("请输入有效账号与不少于 6 位的密码"))) return } var records = loadAllRecords() // Match identifier by email, phone, or matching username/id guard let matchingIndex = records.firstIndex(where: { rec in rec.user.email?.lowercased() == identifier || rec.user.phoneNumber == identifier || rec.user.username.lowercased() == identifier || rec.user.id == identifier }) else { // Auto-fallback for demo convenience if no users registered yet, or error out: completion(.failure(.invalidCredentials)) return } let record = records[matchingIndex] if record.passwordHash == hashPassword(password) { var updatedUser = record.user updatedUser.token = "jwt_token_" + UUID().uuidString.prefix(12) records[matchingIndex].user = updatedUser saveAllRecords(records) saveCurrentSession(updatedUser) completion(.success(updatedUser)) } else { completion(.failure(.invalidCredentials)) } } func register(request: RegisterRequest, completion: @escaping (AuthResult) -> Void) { let username = request.username.trimmingCharacters(in: .whitespacesAndNewlines) let identifier = request.identifier.trimmingCharacters(in: .whitespacesAndNewlines) let password = request.password guard !username.isEmpty else { completion(.failure(.invalidInput("请输入用户名/昵称"))) return } guard !identifier.isEmpty else { completion(.failure(.invalidInput("请输入手机号或邮箱"))) return } guard password.count >= 6 else { completion(.failure(.invalidInput("密码长度不能小于 6 位"))) return } var records = loadAllRecords() let identifierLower = identifier.lowercased() let alreadyExists = records.contains { rec in rec.user.email?.lowercased() == identifierLower || rec.user.phoneNumber == identifierLower || rec.user.username.lowercased() == username.lowercased() } if alreadyExists { completion(.failure(.userAlreadyExists)) return } let isEmail = identifier.contains("@") let newUser = UserAccount( id: "usr_" + UUID().uuidString.prefix(8), username: username, email: isEmail ? identifier : nil, phoneNumber: isEmail ? nil : identifier, avatarURL: "preset_avatar_\((records.count % 5) + 1)", token: "jwt_token_" + UUID().uuidString.prefix(12), registeredAt: Date() ) let record = LocalUserRecord(user: newUser, passwordHash: hashPassword(password)) records.append(record) saveAllRecords(records) saveCurrentSession(newUser) completion(.success(newUser)) } func updateProfile(request: UpdateProfileRequest, completion: @escaping (AuthResult) -> Void) { guard var currentUser = getCurrentUser() else { completion(.failure(.userNotFound)) return } if let username = request.username, !username.trimmingCharacters(in: .whitespaces).isEmpty { currentUser.username = username } if let email = request.email { currentUser.email = email.isEmpty ? nil : email } if let phone = request.phoneNumber { currentUser.phoneNumber = phone.isEmpty ? nil : phone } if let avatar = request.avatarURL { currentUser.avatarURL = avatar } var records = loadAllRecords() if let idx = records.firstIndex(where: { $0.user.id == currentUser.id }) { records[idx].user = currentUser saveAllRecords(records) } saveCurrentSession(currentUser) completion(.success(currentUser)) } func changePassword(request: ChangePasswordRequest, completion: @escaping (AuthResult) -> Void) { guard let currentUser = getCurrentUser() else { completion(.failure(.userNotFound)) return } guard request.newPassword.count >= 6 else { completion(.failure(.invalidInput("新密码长度不能少于 6 位"))) return } var records = loadAllRecords() guard let idx = records.firstIndex(where: { $0.user.id == currentUser.id }) else { completion(.failure(.userNotFound)) return } if records[idx].passwordHash != hashPassword(request.oldPassword) { completion(.failure(.incorrectPassword)) return } records[idx].passwordHash = hashPassword(request.newPassword) saveAllRecords(records) completion(.success(true)) } func logout(completion: @escaping () -> Void) { UserDefaults.standard.removeObject(forKey: currentUserKey) completion() } func getCurrentUser() -> UserAccount? { guard let data = UserDefaults.standard.data(forKey: currentUserKey), let user = try? JSONDecoder().decode(UserAccount.self, from: data) else { return nil } return user } // MARK: - Private Helpers private func loadAllRecords() -> [LocalUserRecord] { guard let data = UserDefaults.standard.data(forKey: usersKey), let records = try? JSONDecoder().decode([LocalUserRecord].self, from: data) else { return [] } return records } private func saveAllRecords(_ records: [LocalUserRecord]) { if let data = try? JSONEncoder().encode(records) { UserDefaults.standard.set(data, forKey: usersKey) } } private func saveCurrentSession(_ user: UserAccount) { if let data = try? JSONEncoder().encode(user) { UserDefaults.standard.set(data, forKey: currentUserKey) } } private func hashPassword(_ raw: String) -> String { return "hashed_" + String(raw.reversed()) + "_salt" } }