import 'package:flutter_bloc/flutter_bloc.dart'; import '../../core/services/storage_service.dart'; import '../../core/utils/app_logger.dart'; import '../../data/models/user_model.dart'; import '../../data/repositories/app_repositories.dart'; abstract class AuthState {} class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} class AuthOtpSent extends AuthState { final String phoneNumber; final String role; AuthOtpSent({required this.phoneNumber, required this.role}); } class AuthNeedsNationalId extends AuthState { final String identityToken; final String phoneNumber; AuthNeedsNationalId({required this.identityToken, required this.phoneNumber}); } class AuthNeedsOnboarding extends AuthState { final String identityToken; final String nationalId; AuthNeedsOnboarding({required this.identityToken, required this.nationalId}); } class Authenticated extends AuthState { final UserModel user; final String activeRole; Authenticated({required this.user, required this.activeRole}); } class Unauthenticated extends AuthState {} class AuthError extends AuthState { final String message; AuthError(this.message); } class AuthCubit extends Cubit { final AuthRepository _authRepo; final StorageService _storage; AuthCubit({AuthRepository? authRepo, StorageService? storage}) : _authRepo = authRepo ?? AuthRepository(), _storage = storage ?? StorageService(), super(AuthInitial()); Future checkSession() async { AppLogger.log('Checking local session...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final token = await _storage.getToken(); if (token == null || token.isEmpty) { AppLogger.log('No token found -> Unauthenticated', tag: 'AUTH_CUBIT'); emit(Unauthenticated()); return; } AppLogger.log('Token found -> Validating with /api/auth/me...', tag: 'AUTH_CUBIT'); final user = await _authRepo.getMe(); if (user != null) { final role = await _storage.getActiveRole() ?? user.role; AppLogger.log('User authenticated: ${user.name} (Role: $role)', tag: 'AUTH_CUBIT'); emit(Authenticated(user: user, activeRole: role)); } else { AppLogger.log('Failed to fetch user -> Unauthenticated', tag: 'AUTH_CUBIT'); emit(Unauthenticated()); } } catch (e) { AppLogger.log('Session check error: $e', tag: 'AUTH_CUBIT'); emit(Unauthenticated()); } } Future sendOtp(String phoneNumber, {String role = 'student'}) async { AppLogger.log('Requesting OTP for $phoneNumber (Role: $role)...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final cleanPhone = phoneNumber.trim(); final res = await _authRepo.requestOtp(cleanPhone, role: role); if (res['status'] == 'success') { AppLogger.log('OTP sent successfully to $cleanPhone', tag: 'AUTH_CUBIT'); emit(AuthOtpSent(phoneNumber: cleanPhone, role: role)); } else { final msg = res['message']?.toString() ?? 'فشل إرسال رمز التحقق'; AppLogger.log('OTP request rejected: $msg', tag: 'AUTH_CUBIT'); emit(AuthError(msg)); } } catch (e) { AppLogger.error('OTP Send Exception', error: e, tag: 'AUTH_CUBIT'); emit(AuthError(e.toString())); } } Future verifyOtp(String phoneNumber, String otp, {String role = 'student'}) async { AppLogger.log('Verifying OTP for $phoneNumber...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final res = await _authRepo.verifyOtp(phoneNumber, otp.trim(), role: role); final data = res['data']; if (data != null && data['token'] != null) { final user = UserModel.fromJson(Map.from(data['user'])); AppLogger.log('OTP verified -> Authenticated: ${user.name}', tag: 'AUTH_CUBIT'); emit(Authenticated(user: user, activeRole: role)); } else if (data != null && data['identity_token'] != null) { AppLogger.log('OTP verified -> Requires National ID', tag: 'AUTH_CUBIT'); emit(AuthNeedsNationalId( identityToken: data['identity_token'].toString(), phoneNumber: phoneNumber, )); } else { final msg = res['message']?.toString() ?? 'رمز التحقق غير صحيح'; AppLogger.log('OTP Verification Failed: $msg', tag: 'AUTH_CUBIT'); emit(AuthError(msg)); } } catch (e) { AppLogger.error('OTP Verify Exception', error: e, tag: 'AUTH_CUBIT'); emit(AuthError(e.toString())); } } Future verifyNationalId(String identityToken, String nationalId) async { AppLogger.log('Verifying National ID: $nationalId...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final res = await _authRepo.verifyNationalId(identityToken, nationalId.trim()); final data = res['data']; if (data != null && data['requires_onboarding'] == true) { AppLogger.log('National ID not registered -> Requires Onboarding', tag: 'AUTH_CUBIT'); emit(AuthNeedsOnboarding( identityToken: identityToken, nationalId: nationalId.trim(), )); } else if (data != null && data['user'] != null) { final user = UserModel.fromJson(Map.from(data['user'])); AppLogger.log('National ID verified -> Student authenticated: ${user.name}', tag: 'AUTH_CUBIT'); emit(Authenticated(user: user, activeRole: 'student')); } else { emit(AuthError(res['message']?.toString() ?? 'فشل التحقق من الرقم الوطني')); } } catch (e) { AppLogger.error('National ID Exception', error: e, tag: 'AUTH_CUBIT'); emit(AuthError(e.toString())); } } Future setupProfile({ required String identityToken, required String fullName, required String nationalId, String gradeLevel = 'grade_10', String stream = 'scientific', }) async { AppLogger.log('Setting up profile for $fullName ($nationalId)...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final user = await _authRepo.setupStudentProfile( identityToken: identityToken, fullName: fullName.trim(), nationalId: nationalId.trim(), gradeLevel: gradeLevel, stream: stream, ); AppLogger.log('Profile setup complete -> Student authenticated: ${user.name}', tag: 'AUTH_CUBIT'); emit(Authenticated(user: user, activeRole: 'student')); } catch (e) { AppLogger.error('Setup Profile Exception', error: e, tag: 'AUTH_CUBIT'); emit(AuthError(e.toString())); } } Future updateGrade(String gradeLevel, {String stream = 'general'}) async { final currentState = state; if (currentState is! Authenticated) return; AppLogger.log('Updating student grade to $gradeLevel ($stream)...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); try { final updatedUser = await _authRepo.updateStudentGrade(gradeLevel, stream: stream); AppLogger.log('Grade updated successfully -> ${updatedUser.gradeLevel}', tag: 'AUTH_CUBIT'); emit(Authenticated(user: updatedUser, activeRole: 'student')); } catch (e) { AppLogger.error('Update Grade Exception', error: e, tag: 'AUTH_CUBIT'); emit(AuthError(e.toString())); emit(currentState); } } Future switchRole(String newRole) async { final currentState = state; if (currentState is Authenticated) { AppLogger.log('Switching role from ${currentState.activeRole} to $newRole', tag: 'AUTH_CUBIT'); await _storage.saveActiveRole(newRole); emit(Authenticated(user: currentState.user, activeRole: newRole)); } } Future logout() async { AppLogger.log('Logging out user...', tag: 'AUTH_CUBIT'); emit(AuthLoading()); await _authRepo.logout(); emit(Unauthenticated()); } }