import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; class ApiService { /// Optional override at build time: /// `flutter run --dart-define=API_BASE_URL=https://chitfund.deepteklabs.com/api` static const String _envBaseUrl = String.fromEnvironment('API_BASE_URL'); /// Local backend in **debug** when `API_BASE_URL` is not set (avoids hitting prod by mistake). /// Release/profile builds default to production unless you pass `--dart-define=API_BASE_URL=...`. static String get baseUrl { if (_envBaseUrl.isNotEmpty) return _envBaseUrl; if (kDebugMode) return 'http://localhost:3000/api'; return 'https://chitfund.deepteklabs.com/api'; } static const String tokenKey = 'auth_token'; late Dio _dio; // Singleton pattern static final ApiService _instance = ApiService._internal(); factory ApiService() => _instance; ApiService._internal() { print('🏗️ [ApiService] Creating singleton instance → ${baseUrl}'); _dio = Dio(BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, )); // Add interceptor to include auth token _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { final token = await _getToken(); print('🔐 [ApiService] Token retrieved: ${token != null ? "YES (${token.substring(0, 20)}...)" : "NO - NULL!"}'); print('🔐 [ApiService] Request: ${options.method} ${options.path}'); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; print('🔐 [ApiService] Authorization header added'); } else { print('⚠️ [ApiService] WARNING: No token found! Request will fail!'); } handler.next(options); }, onError: (error, handler) { print('❌ [ApiService] Error: ${error.response?.statusCode} - ${error.message}'); if (error.response?.statusCode == 401) { print('❌ [ApiService] 401 Unauthorized detected'); // Only clear token if the error message indicates token is expired/invalid final errorMessage = error.response?.data?['message']?.toString() ?? ''; if (errorMessage.contains('Token expired') || errorMessage.contains('Invalid token') || errorMessage.contains('Authentication required')) { print('❌ [ApiService] Token is invalid/expired - Clearing token'); _clearToken(); } else { print('⚠️ [ApiService] 401 but token might be valid - NOT clearing (backend issue?)'); } } handler.next(error); }, )); } // Token management Future _getToken() async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString(tokenKey); print('🔑 [ApiService._getToken] Looking for key: "$tokenKey"'); print('🔑 [ApiService._getToken] Token found: ${token != null}'); if (token != null) { print('🔑 [ApiService._getToken] Token length: ${token.length}'); print('🔑 [ApiService._getToken] Token starts with: ${token.substring(0, token.length > 20 ? 20 : token.length)}...'); } return token; } catch (e) { print('❌ [ApiService._getToken] Error: $e'); return null; } } Future _saveToken(String token) async { try { final prefs = await SharedPreferences.getInstance(); await prefs.setString(tokenKey, token); print('✅ [ApiService._saveToken] Token saved with key: "$tokenKey"'); print('✅ [ApiService._saveToken] Token length: ${token.length}'); } catch (e) { print('❌ [ApiService._saveToken] Error: $e'); } } Future _clearToken() async { try { final prefs = await SharedPreferences.getInstance(); await prefs.remove(tokenKey); print('🗑️ [ApiService._clearToken] Token cleared'); } catch (e) { print('❌ [ApiService._clearToken] Error: $e'); } } // Authentication APIs Future> signup(String mobileNumber, String fullName, String password, {String? email, String? address, String? emergencyContact}) async { try { final data = { 'mobile_number': mobileNumber, 'full_name': fullName, 'password': password, }; if (email != null && email.isNotEmpty) data['email'] = email; if (address != null && address.isNotEmpty) data['address'] = address; if (emergencyContact != null && emergencyContact.isNotEmpty) data['emergency_contact'] = emergencyContact; final response = await _dio.post('/auth/signup', data: data); if (response.data['success']) { await _saveToken(response.data['data']['token']); } return response.data; } on DioException catch (e) { return _handleError(e); } } Future> login(String mobileNumber, String password) async { try { final response = await _dio.post('/auth/login', data: { 'mobile_number': mobileNumber, 'password': password, }); if (response.data['success']) { await _saveToken(response.data['data']['token']); } return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getProfile() async { try { final response = await _dio.get('/auth/profile'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updateProfile(Map data) async { try { final response = await _dio.put('/auth/profile', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> changePassword(String currentPassword, String newPassword) async { try { final response = await _dio.put('/auth/change-password', data: { 'current_password': currentPassword, 'new_password': newPassword, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> createMember(String mobileNumber, String fullName, {String? email, String? address, String? emergencyContact}) async { try { final data = { 'mobile_number': mobileNumber, 'full_name': fullName, }; if (email != null && email.isNotEmpty) data['email'] = email; if (address != null && address.isNotEmpty) data['address'] = address; if (emergencyContact != null && emergencyContact.isNotEmpty) data['emergency_contact'] = emergencyContact; final response = await _dio.post('/auth/create-member', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } // Chit Group APIs Future> createChitGroup(Map data) async { try { final response = await _dio.post('/chit-groups', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getManagerChitGroups({String? status, int page = 1, int limit = 10}) async { try { final response = await _dio.get('/chit-groups/manager', queryParameters: { if (status != null) 'status': status, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getMemberChitGroups({String? status, int page = 1, int limit = 10}) async { try { final response = await _dio.get('/chit-groups/member', queryParameters: { if (status != null) 'status': status, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getChitGroupDetails(String groupId) async { try { final response = await _dio.get('/chit-groups/$groupId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updateChitGroup(String groupId, Map data) async { try { final response = await _dio.put('/chit-groups/$groupId', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> deleteChitGroup(String groupId) async { try { final response = await _dio.delete('/chit-groups/$groupId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> startChitGroup(String groupId) async { try { final response = await _dio.post('/chit-groups/$groupId/start'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getChitGroupStats(String groupId) async { try { final response = await _dio.get('/chit-groups/$groupId/stats'); return response.data; } on DioException catch (e) { return _handleError(e); } } // Member Management APIs Future> addMemberToGroup(String groupId, Map memberData) async { try { final response = await _dio.post('/members/$groupId/members', data: memberData); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> removeMemberFromGroup(String groupId, String memberId) async { try { final response = await _dio.delete('/members/$groupId/members/$memberId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getAllUsers({String? search, int page = 1, int limit = 50}) async { try { final queryParams = { 'page': page, 'limit': limit, }; if (search != null && search.isNotEmpty) { queryParams['search'] = search; } final response = await _dio.get('/members/users', queryParameters: queryParams); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getAvailableUsersForGroup(String groupId) async { try { final response = await _dio.get('/members/users/available/$groupId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getGroupMembers(String groupId, {String? status, int page = 1, int limit = 20}) async { try { final response = await _dio.get('/members/$groupId/members', queryParameters: { if (status != null) 'status': status, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getMemberDetails(String groupId, String memberId) async { try { final response = await _dio.get('/members/$groupId/members/$memberId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updateMemberStatus(String groupId, String memberId, String status) async { try { final response = await _dio.put('/members/$groupId/members/$memberId/status', data: { 'status': status, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getMemberPaymentSummary(String groupId, String memberId) async { try { final response = await _dio.get('/members/$groupId/members/$memberId/payment-summary'); return response.data; } on DioException catch (e) { return _handleError(e); } } // Payment Management APIs Future> recordPayment(Map data) async { try { final response = await _dio.post('/payments', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getGroupPayments(String groupId, {String? status, int? month, int? year, int page = 1, int limit = 20}) async { try { final response = await _dio.get('/payments/group/$groupId', queryParameters: { if (status != null) 'status': status, if (month != null) 'month': month, if (year != null) 'year': year, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getMemberPayments(String groupId, String memberId, {String? status, int page = 1, int limit = 20}) async { try { final response = await _dio.get('/payments/group/$groupId/member/$memberId', queryParameters: { if (status != null) 'status': status, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updatePaymentStatus(String paymentId, String status, {String? notes}) async { try { final response = await _dio.put('/payments/$paymentId/status', data: { 'status': status, if (notes != null) 'notes': notes, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getGroupPaymentSummary(String groupId) async { try { final response = await _dio.get('/payments/group/$groupId/summary'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getPendingPayments(String groupId) async { try { final response = await _dio.get('/payments/group/$groupId/pending'); return response.data; } on DioException catch (e) { return _handleError(e); } } // Monthly Draw APIs Future> createMonthlyDraw(Map data) async { try { final response = await _dio.post('/monthly-draws', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getGroupMonthlyDraws(String groupId, {String? status, int page = 1, int limit = 20}) async { try { final response = await _dio.get('/monthly-draws/group/$groupId', queryParameters: { if (status != null) 'status': status, 'page': page, 'limit': limit, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getMonthlyDrawDetails(String drawId) async { try { final response = await _dio.get('/monthly-draws/$drawId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> verifyDrawResult(String drawId, {String? clientSeed}) async { try { final response = await _dio.post('/monthly-draws/$drawId/verify', data: { if (clientSeed != null) 'client_seed': clientSeed, }); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getDrawStatistics(String groupId) async { try { final response = await _dio.get('/monthly-draws/group/$groupId/statistics'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updateMonthlyDraw(String drawId, Map data) async { try { final response = await _dio.put('/monthly-draws/$drawId', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> deleteMonthlyDraw(String drawId) async { try { final response = await _dio.delete('/monthly-draws/$drawId'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> getGroupFinancialData(String groupId) async { try { final response = await _dio.get('/chit-groups/$groupId/financial-data'); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> updateMemberDetails(String memberId, Map data) async { try { final response = await _dio.put('/auth/member/$memberId', data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } // Generic HTTP Methods Future> get(String path, {Map? queryParameters}) async { try { final response = await _dio.get(path, queryParameters: queryParameters); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> post(String path, Map data) async { try { final response = await _dio.post(path, data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> put(String path, Map data) async { try { final response = await _dio.put(path, data: data); return response.data; } on DioException catch (e) { return _handleError(e); } } Future> delete(String path) async { try { final response = await _dio.delete(path); return response.data; } on DioException catch (e) { return _handleError(e); } } // Logout Future logout() async { await _clearToken(); } // Debug: Check if token exists Future hasToken() async { final token = await _getToken(); return token != null; } // Debug: Get current token (for debugging) Future getCurrentToken() async { return await _getToken(); } // Error handling Map _handleError(DioException e) { if (e.response != null) { final responseData = e.response?.data; String message = 'Request failed'; if (responseData is Map) { message = responseData['message']?.toString() ?? 'Request failed'; final merged = Map.from(responseData); merged['success'] = false; merged['message'] = message; merged['statusCode'] = e.response?.statusCode; return merged; } else if (responseData is String) { message = responseData; } return { 'success': false, 'message': message, 'statusCode': e.response?.statusCode, }; } else { return { 'success': false, 'message': 'Network error: ${e.message}', 'statusCode': 0, }; } } }