import 'dart:convert'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:logger/logger.dart'; import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; final Logger logger = Logger(); class ApiService { static const Duration timeout = Duration(seconds: 10); static const bool enableLogs = true; // === Helpers === static Future _getToken() async { final token = await LocalStorage.getJwtToken(); if (token == null && enableLogs) logger.w("No JWT token found."); return token; } static Map _headers(String token) => { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token', }; static void _log(String message) { if (enableLogs) logger.i(message); } static dynamic _parseResponse(http.Response response, {String label = ''}) { _log("$label Response: ${response.body}"); try { final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { return json['data']; } _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); } catch (e) { _log("Response parsing error [$label]: $e"); } return null; } static dynamic _parseResponseForAllData(http.Response response, {String label = ''}) { _log("$label Response: ${response.body}"); try { final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { return json; } _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); } catch (e) { _log("Response parsing error [$label]: $e"); } return null; } static Future _getRequest( String endpoint, { Map? queryParams, bool hasRetried = false, }) async { String? token = await _getToken(); if (token == null) return null; final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint").replace(queryParameters: queryParams); _log("GET $uri"); try { final response = await http.get(uri, headers: _headers(token)).timeout(timeout); if (response.statusCode == 401 && !hasRetried) { _log("Unauthorized. Attempting token refresh..."); if (await AuthService.refreshToken()) { return await _getRequest(endpoint, queryParams: queryParams, hasRetried: true); } _log("Token refresh failed."); } return response; } catch (e) { _log("HTTP GET Exception: $e"); return null; } } static Future _postRequest( String endpoint, dynamic body, { Duration customTimeout = timeout, bool hasRetried = false, }) async { String? token = await _getToken(); if (token == null) return null; final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint"); _log("POST $uri\nHeaders: ${_headers(token)}\nBody: $body"); try { final response = await http .post(uri, headers: _headers(token), body: jsonEncode(body)) .timeout(customTimeout); if (response.statusCode == 401 && !hasRetried) { _log("Unauthorized POST. Attempting token refresh..."); if (await AuthService.refreshToken()) { return await _postRequest(endpoint, body, customTimeout: customTimeout, hasRetried: true); } } return response; } catch (e) { _log("HTTP POST Exception: $e"); return null; } } // === Attendance APIs === static Future?> getProjects() async => _getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Projects') : null); static Future?> getGlobalProjects() async => _getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Global Projects') : null); static Future?> getEmployeesByProject(String projectId) async => _getRequest(ApiEndpoints.getEmployeesByProject, queryParams: {"projectId": projectId}) .then((res) => res != null ? _parseResponse(res, label: 'Employees') : null); static Future?> getAttendanceLogs( String projectId, { DateTime? dateFrom, DateTime? dateTo, }) async { final query = { "projectId": projectId, if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), }; return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query) .then((res) => res != null ? _parseResponse(res, label: 'Attendance Logs') : null); } static Future?> getAttendanceLogView(String id) async => _getRequest("${ApiEndpoints.getAttendanceLogView}/$id") .then((res) => res != null ? _parseResponse(res, label: 'Log Details') : null); static Future?> getRegularizationLogs(String projectId) async => _getRequest(ApiEndpoints.getRegularizationLogs, queryParams: {"projectId": projectId}) .then((res) => res != null ? _parseResponse(res, label: 'Regularization Logs') : null); static Future uploadAttendanceImage( String id, String employeeId, XFile? imageFile, double latitude, double longitude, { required String imageName, required String projectId, String comment = "", required int action, bool imageCapture = true, String? markTime, }) async { final now = DateTime.now(); final body = { "id": id, "employeeId": employeeId, "projectId": projectId, "markTime": markTime ?? DateFormat('hh:mm a').format(now), "comment": comment, "action": action, "date": DateFormat('yyyy-MM-dd').format(now), if (imageCapture) "latitude": '$latitude', if (imageCapture) "longitude": '$longitude', }; if (imageCapture && imageFile != null) { try { final bytes = await imageFile.readAsBytes(); final fileSize = await imageFile.length(); final contentType = "image/${imageFile.path.split('.').last}"; body["image"] = { "fileName": imageName, "contentType": contentType, "fileSize": fileSize, "description": "Employee attendance photo", "base64Data": base64Encode(bytes), }; } catch (e) { _log("Image encoding error: $e"); return false; } } final response = await _postRequest(ApiEndpoints.uploadAttendanceImage, body); if (response == null) return false; final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) return true; _log("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); return false; } static String generateImageName(String employeeId, int count) { final now = DateTime.now(); final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now); final imageNumber = count.toString().padLeft(3, '0'); return "${employeeId}_${dateStr}_$imageNumber.jpg"; } // === Employee APIs === static Future?> getAllEmployeesByProject(String projectId) async { if (projectId.isEmpty) throw ArgumentError('projectId must not be empty'); final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId"; return _getRequest(endpoint).then((res) => res != null ? _parseResponse(res, label: 'Employees by Project') : null); } static Future?> getAllEmployees() async => _getRequest(ApiEndpoints.getAllEmployees).then((res) => res != null ? _parseResponse(res, label: 'All Employees') : null); static Future?> getRoles() async => _getRequest(ApiEndpoints.getRoles).then((res) => res != null ? _parseResponse(res, label: 'Roles') : null); static Future createEmployee({ required String firstName, required String lastName, required String phoneNumber, required String gender, required String jobRoleId, }) async { final body = { "firstName": firstName, "lastName": lastName, "phoneNumber": phoneNumber, "gender": gender, "jobRoleId": jobRoleId, }; final response = await _postRequest(ApiEndpoints.createEmployee, body); if (response == null) return false; final json = jsonDecode(response.body); return response.statusCode == 200 && json['success'] == true; } static Future?> getEmployeeDetails(String employeeId) async { final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId"; final response = await _getRequest(url); final data = response != null ? _parseResponse(response, label: 'Employee Details') : null; return data is Map ? data : null; } // === Daily Task APIs === static Future?> getDailyTasks( String projectId, { DateTime? dateFrom, DateTime? dateTo, }) async { final query = { "projectId": projectId, if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), }; return _getRequest(ApiEndpoints.getDailyTask, queryParams: query) .then((res) => res != null ? _parseResponse(res, label: 'Daily Tasks') : null); } static Future reportTask({ required String id, required int completedTask, required String comment, required List> checkList, List>? images, }) async { final body = { "id": id, "completedTask": completedTask, "comment": comment, "reportedDate": DateTime.now().toUtc().toIso8601String(), "checkList": checkList, if (images != null && images.isNotEmpty) "images": images, }; final response = await _postRequest(ApiEndpoints.reportTask, body); if (response == null) return false; final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { Get.back(); return true; } _log("Failed to report task: ${json['message'] ?? 'Unknown error'}"); return false; } static Future commentTask({ required String id, required String comment, List>? images, }) async { final body = { "taskAllocationId": id, "comment": comment, "commentDate": DateTime.now().toUtc().toIso8601String(), if (images != null && images.isNotEmpty) "images": images, }; final response = await _postRequest(ApiEndpoints.commentTask, body); if (response == null) return false; final json = jsonDecode(response.body); return response.statusCode == 200 && json['success'] == true; } static Future?> getDailyTasksDetails(String projectId) async { final url = "${ApiEndpoints.dailyTaskDetails}/$projectId"; final response = await _getRequest(url); return response != null ? _parseResponseForAllData(response, label: 'Daily Task Details') as Map? : null; } static Future assignDailyTask({ required String workItemId, required int plannedTask, required String description, required List taskTeam, DateTime? assignmentDate, }) async { final body = { "workItemId": workItemId, "plannedTask": plannedTask, "description": description, "taskTeam": taskTeam, "assignmentDate": (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(), }; final response = await _postRequest(ApiEndpoints.assignDailyTask, body); if (response == null) return false; final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { Get.back(); return true; } _log("Failed to assign daily task: ${json['message'] ?? 'Unknown error'}"); return false; } }