import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:get/get.dart'; import 'package:on_field_work/controller/project_controller.dart'; 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/services/auth_service.dart'; import 'package:on_field_work/model/tenant/tenant_list_model.dart'; import 'package:on_field_work/helpers/utils/encryption_helper.dart'; abstract class ITenantService { Future>?> getTenants({bool hasRetried = false}); Future selectTenant(String tenantId, {bool hasRetried = false}); } class TenantService implements ITenantService { static const String _baseUrl = ApiEndpoints.baseUrl; static const Map _headers = { 'Content-Type': 'application/json', }; static Tenant? currentTenant; static void setSelectedTenant(Tenant tenant) { currentTenant = tenant; } static bool get isTenantSelected => currentTenant != null; static Future> _authorizedHeaders() async { final token = await LocalStorage.getJwtToken(); if (token == null || token.isEmpty) throw Exception('Missing JWT token'); return {..._headers, 'Authorization': 'Bearer $token'}; } static void _handleApiError(http.Response response, dynamic data, String context) { final message = data['message'] ?? 'Unknown error'; final level = response.statusCode >= 500 ? LogLevel.error : LogLevel.warning; logSafe("❌ $context failed: $message [Status: ${response.statusCode}]", level: level); } static void _logException(dynamic e, dynamic st, String context) { logSafe("❌ $context exception", level: LogLevel.error, error: e, stackTrace: st); } @override Future>?> getTenants({bool hasRetried = false}) async { try { final headers = await _authorizedHeaders(); final response = await http.get(Uri.parse("$_baseUrl/auth/get/user/tenants"), headers: headers); if (response.body.isEmpty || response.body.trim().isEmpty) { logSafe("❌ Empty tenant response — auto logout"); await LocalStorage.logout(); return null; } final decrypted = decryptResponse(response.body); if (decrypted == null) { logSafe("❌ Tenant response decryption failed — auto logout"); await LocalStorage.logout(); return null; } final data = decrypted is Map ? decrypted : null; if (data == null) { logSafe("❌ Decrypted tenant data is not valid JSON — auto logout"); await LocalStorage.logout(); return null; } if (response.statusCode == 200 && data['success'] == true) { final list = data['data']; if (list is! List) return null; return List>.from(list); } if (response.statusCode == 401 && !hasRetried) { final refreshed = await AuthService.refreshToken(); if (refreshed) return getTenants(hasRetried: true); return null; } _handleApiError(response, data, "Fetching tenants"); return null; } catch (e, st) { _logException(e, st, "Get Tenants API"); return null; } } @override Future selectTenant(String tenantId, {bool hasRetried = false}) async { try { final headers = await _authorizedHeaders(); logSafe("➡️ POST $_baseUrl/auth/select-tenant/$tenantId\nHeaders: $headers", level: LogLevel.info); final response = await http.post(Uri.parse("$_baseUrl/auth/select-tenant/$tenantId"), headers: headers); final decrypted = decryptResponse(response.body); if (decrypted == null) { logSafe("❌ Tenant selection response decryption failed", level: LogLevel.error); return false; } final data = decrypted is Map ? decrypted : null; if (data == null) { logSafe("❌ Decrypted tenant selection data is not valid JSON", level: LogLevel.error); return false; } logSafe("⬅️ Response: ${jsonEncode(data)} [Status: ${response.statusCode}]", level: LogLevel.info); if (response.statusCode == 200 && data['success'] == true) { await LocalStorage.setJwtToken(data['data']['token']); await LocalStorage.setRefreshToken(data['data']['refreshToken']); logSafe("✅ Tenant selected successfully. Tokens updated."); try { final projectController = Get.find(); projectController.clearProjects(); projectController.fetchProjects(); } catch (_) { logSafe("⚠️ ProjectController not found while refreshing projects"); } final fcmToken = LocalStorage.getFcmToken(); if (fcmToken?.isNotEmpty ?? false) { final success = await AuthService.registerDeviceToken(fcmToken!); logSafe(success ? "✅ FCM token registered after tenant selection." : "⚠️ Failed to register FCM token.", level: success ? LogLevel.info : LogLevel.warning); } return true; } if (response.statusCode == 401 && !hasRetried) { logSafe("⚠️ Unauthorized while selecting tenant. Refreshing token...", level: LogLevel.warning); final refreshed = await AuthService.refreshToken(); if (refreshed) return selectTenant(tenantId, hasRetried: true); logSafe("❌ Token refresh failed while selecting tenant.", level: LogLevel.error); return false; } _handleApiError(response, data, "Selecting tenant"); return false; } catch (e, st) { _logException(e, st, "Select Tenant API"); return false; } } }