import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/services/app_logger.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 headers = { ..._headers, 'Authorization': 'Bearer $token', }; final endpoint = "$_baseUrl/auth/set/device-token"; // 🔹 Log request details logSafe("📡 Device Token API Request"); logSafe("➡️ Endpoint: $endpoint"); logSafe("➡️ Headers: ${jsonEncode(headers)}"); logSafe("➡️ Payload: ${jsonEncode(body)}"); 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..."); logSafe("Login payload (raw): $data"); logSafe("Login payload (JSON): ${jsonEncode(data)}"); 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."); // 🔹 Retry FCM token registration after token refresh final newFcmToken = LocalStorage.getFcmToken(); if (newFcmToken?.isNotEmpty ?? false) { final success = await registerDeviceToken(newFcmToken!); logSafe( success ? "✅ FCM token re-registered after JWT refresh." : "⚠️ Failed to register FCM token after JWT refresh.", level: success ? LogLevel.info : LogLevel.warning); } 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)); return { ...jsonDecode(response.body), "statusCode": response.statusCode, }; } 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); return { ...jsonDecode(response.body), "statusCode": response.statusCode, }; } 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 { logSafe("Processing login success..."); 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; logSafe("✅ Login flow completed and controllers initialized."); } }