import 'package:dio/dio.dart'; import 'package:real_estate_mobile/core/constants/api_constants.dart'; import 'package:real_estate_mobile/core/network/api_client.dart'; import 'package:real_estate_mobile/core/network/api_exceptions.dart'; import 'package:real_estate_mobile/core/storage/secure_storage.dart'; import 'package:real_estate_mobile/features/auth/data/models/auth_response.dart'; import 'package:real_estate_mobile/features/auth/data/models/register_request.dart'; import 'package:real_estate_mobile/features/auth/data/models/user_model.dart'; class AuthRepository { final Dio _dio = ApiClient.instance.dio; /// Extract a user-friendly message from a DioException. /// NEVER returns Dio's raw technical text — always a short human sentence. static String _friendlyError(DioException e, String fallback) { // If our interceptor already wrapped it, use that message if (e.error is ApiException) return (e.error as ApiException).message; // Status-code based fallback switch (e.response?.statusCode) { case 400: return 'The request was invalid. Please check your input.'; case 401: return 'Invalid email or password.'; case 403: return 'You don\'t have permission to perform this action.'; case 404: return 'Not found.'; case 409: return 'This action conflicts with existing data.'; case 429: return 'Too many attempts. Please wait and try again.'; } if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout || e.type == DioExceptionType.sendTimeout) { return 'Request timed out. Please check your connection.'; } if (e.type == DioExceptionType.connectionError) { return 'Unable to reach the server. Check your internet connection.'; } return fallback; } Future> login({ required String email, required String password, }) async { try { final response = await _dio.post( ApiConstants.authLogin, data: {'email': email, 'password': password}, ); final data = response.data['data'] as Map; // Check if 2FA is required if (data['requiresTwoFactor'] == true) { return { 'requiresTwoFactor': true, 'tempToken': data['tempToken'], }; } final authResponse = AuthResponse.fromJson(data); await SecureStorage.saveTokens( accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken, ); return { 'requiresTwoFactor': false, 'user': authResponse.user, }; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, 'Login failed'), statusCode: e.response?.statusCode, ); } } /// Verify 2FA TOTP token during login Future verifyTwoFactor({ required String tempToken, required String token, }) async { try { final response = await _dio.post( ApiConstants.twoFactorVerify, data: {'tempToken': tempToken, 'token': token}, ); final data = response.data['data'] as Map; final authResponse = AuthResponse.fromJson(data); await SecureStorage.saveTokens( accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken, ); return authResponse; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, '2FA verification failed'), statusCode: e.response?.statusCode, ); } } /// Verify backup code during login Future verifyBackupCode({ required String tempToken, required String backupCode, }) async { try { final response = await _dio.post( ApiConstants.twoFactorVerifyBackup, data: {'tempToken': tempToken, 'backupCode': backupCode}, ); final data = response.data['data'] as Map; final authResponse = AuthResponse.fromJson(data); await SecureStorage.saveTokens( accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken, ); return authResponse; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, 'Backup code verification failed'), statusCode: e.response?.statusCode, ); } } Future getMe() async { try { final response = await _dio.get(ApiConstants.authMe); final data = response.data['data'] as Map; return UserModel.fromJson(data); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, 'Failed to fetch user'), statusCode: e.response?.statusCode, ); } } Future logout() async { try { await _dio.post(ApiConstants.authLogout); } catch (_) { // Ignore logout API errors — still clear local tokens } finally { await SecureStorage.clearTokens(); } } /// Social auth (Google, Facebook, etc.) — calls POST /auth/social Future socialAuth({ required String provider, required String providerId, required String email, String? name, String? avatar, String? role, String? agentTypeId, String mode = 'signup', }) async { try { final response = await _dio.post( ApiConstants.authSocial, data: { 'provider': provider, 'providerId': providerId, 'email': email, 'mode': mode, if (name != null) 'name': name, if (avatar != null) 'avatar': avatar, if (role != null) 'role': role, if (agentTypeId != null) 'agentTypeId': agentTypeId, }, ); final data = response.data['data'] as Map; final authResponse = AuthResponse.fromJson(data); await SecureStorage.saveTokens( accessToken: authResponse.accessToken, refreshToken: authResponse.refreshToken, ); return authResponse.user; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, 'Social login failed'), statusCode: e.response?.statusCode, ); } } /// Register returns { message, user } — no tokens. /// The user must verify their email before logging in. Future> register(RegisterRequest request) async { try { final response = await _dio.post( ApiConstants.authRegister, data: request.toJson(), ); final data = response.data['data'] as Map; return data; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: _friendlyError(e, 'Registration failed'), statusCode: e.response?.statusCode, ); } } }