import 'package:flutter_riverpod/flutter_riverpod.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/services/push_notification_service.dart'; import 'package:real_estate_mobile/core/storage/secure_storage.dart'; import 'package:real_estate_mobile/features/auth/data/auth_repository.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'; // Auth state enum AuthStatus { initial, loading, authenticated, error } class AuthState { final AuthStatus status; final UserModel? user; final String? errorMessage; final List fieldErrors; final String registeredEmail; final bool requiresTwoFactor; final String? tempToken; final String? twoFactorEmail; const AuthState({ this.status = AuthStatus.initial, this.user, this.errorMessage, this.fieldErrors = const [], this.registeredEmail = '', this.requiresTwoFactor = false, this.tempToken, this.twoFactorEmail, }); AuthState copyWith({ AuthStatus? status, UserModel? user, String? errorMessage, List? fieldErrors, String? registeredEmail, bool? requiresTwoFactor, String? tempToken, String? twoFactorEmail, }) { return AuthState( status: status ?? this.status, user: user ?? this.user, errorMessage: errorMessage, fieldErrors: fieldErrors ?? this.fieldErrors, registeredEmail: registeredEmail ?? this.registeredEmail, requiresTwoFactor: requiresTwoFactor ?? this.requiresTwoFactor, tempToken: tempToken ?? this.tempToken, twoFactorEmail: twoFactorEmail ?? this.twoFactorEmail, ); } String? getFieldError(String fieldName) { final match = fieldErrors.where((e) => e.field == fieldName); if (match.isEmpty) return null; return match.first.errors.isNotEmpty ? match.first.errors.first : null; } } // Auth notifier class AuthNotifier extends StateNotifier { final AuthRepository _repository; AuthNotifier(this._repository) : super(const AuthState(status: AuthStatus.loading)) { // Wire up force logout callback so API client can reset auth state // when token refresh fails ApiClient.onForceLogout = () { state = const AuthState(); }; checkAuthStatus(); } Future checkAuthStatus() async { state = state.copyWith(status: AuthStatus.loading); final token = await SecureStorage.getAccessToken(); if (token == null) { state = state.copyWith(status: AuthStatus.initial); return; } try { final user = await _repository.getMe(); state = state.copyWith( status: AuthStatus.authenticated, user: user, ); // Register for push notifications after auth confirmed _initPushNotifications(); } catch (_) { // Token is invalid/expired — clear and show login await SecureStorage.clearTokens(); state = state.copyWith(status: AuthStatus.initial); } } Future logout() async { state = state.copyWith(status: AuthStatus.loading); // Unregister FCM token before clearing auth await PushNotificationService().unregister(); await _repository.logout(); state = const AuthState(); } Future login({ required String email, required String password, }) async { state = state.copyWith( status: AuthStatus.loading, errorMessage: null, fieldErrors: [], ); try { final result = await _repository.login( email: email, password: password, ); if (result['requiresTwoFactor'] == true) { state = state.copyWith( status: AuthStatus.initial, requiresTwoFactor: true, tempToken: result['tempToken'] as String?, twoFactorEmail: email, ); return; } state = state.copyWith( status: AuthStatus.authenticated, user: result['user'] as UserModel, ); _initPushNotifications(); } on ApiException catch (e) { state = state.copyWith( status: AuthStatus.error, errorMessage: e.fieldErrors.isNotEmpty ? 'Please fix the errors below' : e.message, fieldErrors: e.fieldErrors, ); } catch (e) { state = state.copyWith( status: AuthStatus.error, errorMessage: 'An unexpected error occurred', ); } } Future register({ required String name, required String email, required String password, required String role, }) async { state = state.copyWith( status: AuthStatus.loading, errorMessage: null, fieldErrors: [], ); // Split name into firstName and lastName final nameParts = name.trim().split(RegExp(r'\s+')); final firstName = nameParts.first; final lastName = nameParts.length > 1 ? nameParts.sublist(1).join(' ') : null; try { final user = await _repository.register( RegisterRequest( email: email, password: password, role: role, firstName: firstName, lastName: lastName, ), ); state = state.copyWith( status: AuthStatus.authenticated, user: user, registeredEmail: email, ); _initPushNotifications(); } on ApiException catch (e) { state = state.copyWith( status: AuthStatus.error, errorMessage: e.fieldErrors.isNotEmpty ? 'Please fix the errors below' : e.message, fieldErrors: e.fieldErrors, ); } catch (e) { state = state.copyWith( status: AuthStatus.error, errorMessage: 'An unexpected error occurred', ); } } Future verifyTwoFactor(String token) async { final tempToken = state.tempToken; if (tempToken == null) return; state = state.copyWith( status: AuthStatus.loading, errorMessage: null, ); try { final authResponse = await _repository.verifyTwoFactor( tempToken: tempToken, token: token, ); state = AuthState( status: AuthStatus.authenticated, user: authResponse.user, ); _initPushNotifications(); } on ApiException catch (e) { state = state.copyWith( status: AuthStatus.initial, errorMessage: e.message, ); } catch (e) { state = state.copyWith( status: AuthStatus.initial, errorMessage: 'Verification failed. Please try again.', ); } } Future verifyBackupCode(String backupCode) async { final tempToken = state.tempToken; if (tempToken == null) return; state = state.copyWith( status: AuthStatus.loading, errorMessage: null, ); try { final authResponse = await _repository.verifyBackupCode( tempToken: tempToken, backupCode: backupCode, ); state = AuthState( status: AuthStatus.authenticated, user: authResponse.user, ); _initPushNotifications(); } on ApiException catch (e) { state = state.copyWith( status: AuthStatus.initial, errorMessage: e.message, ); } catch (e) { state = state.copyWith( status: AuthStatus.initial, errorMessage: 'Verification failed. Please try again.', ); } } void clearTwoFactor() { state = const AuthState(); } void _initPushNotifications() { final push = PushNotificationService(); push.initialize().then((_) => push.registerAfterLogin()); } void reset() { state = const AuthState(); } } // Providers final authRepositoryProvider = Provider((ref) { return AuthRepository(); }); final authProvider = StateNotifierProvider((ref) { return AuthNotifier(ref.watch(authRepositoryProvider)); });