import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:on_field_work/helpers/services/api_endpoints.dart'; import 'package:on_field_work/helpers/services/storage/local_storage.dart'; import 'package:on_field_work/helpers/services/app_logger.dart'; import 'package:on_field_work/helpers/utils/encryption_helper.dart'; class AuthService { static const String _baseUrl = ApiEndpoints.baseUrl; static const Map _headers = { 'Content-Type': 'application/json', }; static bool isLoggedIn = false; /* -------------------------------------------------------------------------- / / Logout API / / -------------------------------------------------------------------------- */ static Future logoutApi(String refreshToken, String fcmToken) async { try { final body = {"refreshToken": refreshToken, "fcmToken": fcmToken}; final response = await _post("/auth/logout", body); if (response != null && response['statusCode'] == 200) { logSafe("✅ Logout API successful"); return true; } logSafe("⚠️ Logout API failed: ${response?['message']}", level: LogLevel.warning); return false; } catch (e, st) { _handleError("Logout API error", e, st); return false; } } /* -------------------------------------------------------------------------- / / Public Methods / / -------------------------------------------------------------------------- */ static Future registerDeviceToken(String fcmToken) async { final token = await LocalStorage.getJwtToken(); if (token == null || token.isEmpty) { logSafe("❌ Cannot register device token: missing JWT token", level: LogLevel.warning); return false; } final body = {"fcmToken": fcmToken}; final data = await _post("/auth/set/device-token", body, authToken: token); if (data != null && data['success'] == true) { logSafe("✅ Device token registered successfully."); return true; } logSafe("⚠️ Failed to register device token: ${data?['message']}", level: LogLevel.warning); return false; } static Future?> loginUser( Map data) async { logSafe("Attempting login..."); final responseData = await _post("/auth/app/login", data); if (responseData == null) return {"error": "Network error. Please check your connection."}; if (responseData['data'] != null) { await _handleLoginSuccess(responseData['data']); return null; } if (responseData['statusCode'] == 401) { return {"password": "Invalid email or password"}; } return {"error": responseData['message'] ?? "Unexpected error occurred"}; } static Future refreshToken() async { final accessToken = LocalStorage.getJwtToken(); final refreshToken = LocalStorage.getRefreshToken(); if ([accessToken, refreshToken].any((t) => t == null || t.isEmpty)) { logSafe("Missing access or refresh token.", level: LogLevel.warning); return false; } final body = {"token": accessToken, "refreshToken": refreshToken}; final data = await _post("/auth/refresh-token", body); if (data != null && data['success'] == true) { await LocalStorage.setJwtToken(data['data']['token']); await LocalStorage.setRefreshToken(data['data']['refreshToken']); await LocalStorage.setLoggedInUser(true); logSafe("Token refreshed successfully."); final newFcmToken = LocalStorage.getFcmToken(); if (newFcmToken?.isNotEmpty ?? false) { await registerDeviceToken(newFcmToken!); } return true; } logSafe("Refresh token failed: ${data?['message']}", level: LogLevel.warning); return false; } static Future?> forgotPassword(String email) => _wrapErrorHandling(() => _post("/auth/forgot-password", {"email": email}), successCondition: (data) => data['success'] == true, defaultError: "Failed to send reset link."); static Future?> requestDemo( Map demoData) => _wrapErrorHandling(() => _post("/market/inquiry", demoData), successCondition: (data) => data['success'] == true, defaultError: "Failed to submit demo request."); static Future>?> getIndustries() async { final data = await _get("/market/industries"); if (data != null && data['success'] == true) { return List>.from(data['data']); } return null; } static Future?> generateMpin({ required String employeeId, required String mpin, }) => _wrapErrorHandling( () async { final token = LocalStorage.getJwtToken(); return _post( "/auth/generate-mpin", {"employeeId": employeeId, "mpin": mpin}, authToken: token, ); }, successCondition: (data) => data['success'] == true, defaultError: "Failed to generate MPIN.", ); static Future?> verifyMpin({ required String mpin, required String mpinToken, required String fcmToken, }) => _wrapErrorHandling( () async { final employeeInfo = LocalStorage.getEmployeeInfo(); if (employeeInfo == null) return null; final token = await LocalStorage.getJwtToken(); return _post( "/auth/login-mpin", { "employeeId": employeeInfo.id, "mpin": mpin, "mpinToken": mpinToken, "fcmToken": fcmToken, }, authToken: token, ); }, successCondition: (data) => data['success'] == true, defaultError: "MPIN verification failed.", ); static Future?> generateOtp(String email) => _wrapErrorHandling(() => _post("/auth/send-otp", {"email": email}), successCondition: (data) => data['success'] == true, defaultError: "Failed to generate OTP."); static Future?> verifyOtp({ required String email, required String otp, }) async { final data = await _post("/auth/login-otp", {"email": email, "otp": otp}); if (data != null && data['data'] != null) { await _handleLoginSuccess(data['data']); return null; } return {"error": data?['message'] ?? "OTP verification failed."}; } /* -------------------------------------------------------------------------- */ /* Private Utilities */ /* -------------------------------------------------------------------------- */ static Future?> _post( String path, Map body, { String? authToken, }) async { try { final headers = { ..._headers, if (authToken?.isNotEmpty ?? false) 'Authorization': 'Bearer $authToken', }; final response = await http.post(Uri.parse("$_baseUrl$path"), headers: headers, body: jsonEncode(body)); final decrypted = decryptResponse(response.body); // <-- Decrypt here if (decrypted is Map) { return {"statusCode": response.statusCode, ...decrypted}; } else { return {"statusCode": response.statusCode, "data": decrypted}; } } catch (e, st) { _handleError("$path POST error", e, st); return null; } } static Future?> _get( String path, { String? authToken, }) async { try { final headers = { ..._headers, if (authToken?.isNotEmpty ?? false) 'Authorization': 'Bearer $authToken', }; final response = await http.get(Uri.parse("$_baseUrl$path"), headers: headers); final decrypted = decryptResponse(response.body); // <-- Decrypt here if (decrypted is Map) { return {"statusCode": response.statusCode, ...decrypted}; } else { return {"statusCode": response.statusCode, "data": decrypted}; } } catch (e, st) { _handleError("$path GET error", e, st); return null; } } static Future?> _wrapErrorHandling( Future?> Function() request, { required bool Function(Map data) successCondition, required String defaultError, }) async { final data = await request(); if (data != null && successCondition(data)) return null; return {"error": data?['message'] ?? defaultError}; } static void _handleError(String message, Object error, StackTrace st) { logSafe(message, level: LogLevel.error, error: error, stackTrace: st); } static Future _handleLoginSuccess(Map data) async { await LocalStorage.setJwtToken(data['token']); await LocalStorage.setLoggedInUser(true); if (data['refreshToken'] != null) { await LocalStorage.setRefreshToken(data['refreshToken']); } if (data['mpinToken']?.isNotEmpty ?? false) { await LocalStorage.setMpinToken(data['mpinToken']); await LocalStorage.setIsMpin(true); } else { await LocalStorage.setIsMpin(false); await LocalStorage.removeMpinToken(); } isLoggedIn = true; } }