// lib/features/auth/services/auth_service.dart import 'package:flutter/foundation.dart' show kDebugMode, debugPrint; import 'package:shared_preferences/shared_preferences.dart'; import 'package:google_sign_in/google_sign_in.dart'; import '../../../core/api/api_client.dart'; import '../../../core/api/api_endpoints.dart'; import '../../../core/auth/auth_guard.dart'; import '../../../core/storage/token_storage.dart'; import '../../../core/analytics/posthog_service.dart'; import '../models/user_model.dart'; class AuthService { final ApiClient _api = ApiClient(); /// Google OAuth 2.0 Web Client ID from Google Cloud Console. /// Must match the `GOOGLE_CLIENT_ID` env var set on the Django backend /// so the server can verify the `id_token` audience. /// Source: Google Cloud Console → APIs & Services → Credentials → Web application. static const String _googleWebClientId = '639347358523-mtkm3i8vssuhsun80rp2llt09eou0p8g.apps.googleusercontent.com'; /// LOGIN → returns UserModel Future login(String username, String password) async { try { final res = await _api.post( ApiEndpoints.login, body: { "username": username, "password": password, }, requiresAuth: false, ); final token = res['token']; if (token == null) { throw Exception('Token missing from response'); } final serverUsername = (res['username'] is String && (res['username'] as String).isNotEmpty) ? res['username'] as String : null; final serverEmail = (res['email'] is String && (res['email'] as String).isNotEmpty) ? res['email'] as String : null; // savedEmail is the canonical identifier for this account final savedEmail = serverEmail ?? username; // candidate display name (server username or email fallback) final displayCandidate = serverUsername ?? savedEmail; // clear guest mode on successful login AuthGuard.setGuest(false); // save token (TokenStorage stays responsible for token) await TokenStorage.saveToken(token.toString(), savedEmail); // persist per-account fields in SharedPreferences final prefs = await SharedPreferences.getInstance(); // mark current logged-in account await prefs.setString('current_email', savedEmail); // always keep a canonical 'email' pointing to current user's email for old callers await prefs.setString('email', savedEmail); // save per-account display name (do not overwrite an explicit per-account display name if already set) final perKey = 'display_name_$savedEmail'; final existingPer = prefs.getString(perKey); if (existingPer == null || existingPer.trim().isEmpty) { await prefs.setString(perKey, displayCandidate); } // save server-provided email (if any) separately too if (serverEmail != null) await prefs.setString('server_email', serverEmail); // Save phone if provided (optional) if (res['phone_number'] != null) await prefs.setString('phone_number', res['phone_number'].toString()); // Save profile photo from login response final rawPhoto = res['profile_photo']?.toString() ?? ''; if (rawPhoto.isNotEmpty) { final photoUrl = rawPhoto.startsWith('http') ? rawPhoto : 'https://em.eventifyplus.com$rawPhoto'; await prefs.setString('profileImage_$savedEmail', photoUrl); await prefs.setString('profileImage', photoUrl); } // Save Eventify ID final eventifyId = res['eventify_id']?.toString() ?? ''; if (eventifyId.isNotEmpty) await prefs.setString('eventify_id', eventifyId); PostHogService.instance.identify(savedEmail, properties: { 'username': displayCandidate, 'login_method': 'email', }); PostHogService.instance.capture('user_logged_in'); return UserModel.fromJson(res); } catch (e) { if (kDebugMode) debugPrint('AuthService.login error: $e'); rethrow; } } /// REGISTER → returns UserModel Future register({ required String email, required String phoneNumber, required String password, String? district, }) async { try { final body = { "email": email, "phone_number": phoneNumber, "password": password, }; if (district != null && district.isNotEmpty) { body["district"] = district; } final res = await _api.post( ApiEndpoints.register, body: body, requiresAuth: false, ); final token = res['token']; if (token == null) { throw Exception('Token missing from response'); } final serverUsername = (res['username'] is String && (res['username'] as String).isNotEmpty) ? res['username'] as String : null; final serverEmail = (res['email'] is String && (res['email'] as String).isNotEmpty) ? res['email'] as String : null; final savedEmail = serverEmail ?? email; final savedUsername = serverUsername ?? savedEmail; final savedRole = (res['role'] ?? 'user').toString(); final savedPhone = (res['phone_number'] ?? phoneNumber)?.toString(); // clear guest mode on successful registration AuthGuard.setGuest(false); // Save token + canonical user id for token storage await TokenStorage.saveToken(token.toString(), savedEmail); // Persist per-account keys final prefs = await SharedPreferences.getInstance(); await prefs.setString('current_email', savedEmail); await prefs.setString('email', savedEmail); // Set display_name_ if not present yet final perKey = 'display_name_$savedEmail'; final existing = prefs.getString(perKey); if (existing == null || existing.trim().isEmpty) { await prefs.setString(perKey, savedUsername); } // Optionally save phone/email in prefs await prefs.setString('server_email', savedEmail); if (savedPhone != null) await prefs.setString('phone_number', savedPhone); // Return a simple UserModel (defensive) return UserModel( username: savedUsername, email: savedEmail, role: savedRole, token: token.toString(), phoneNumber: savedPhone, ); } catch (e) { if (kDebugMode) debugPrint('AuthService.register error: $e'); rethrow; } } /// GOOGLE OAUTH LOGIN → returns UserModel Future googleLogin() async { try { final googleSignIn = GoogleSignIn( scopes: const ['email', 'profile'], serverClientId: _googleWebClientId, ); final account = await googleSignIn.signIn(); if (account == null) throw Exception('Google sign-in cancelled'); final auth = await account.authentication; final idToken = auth.idToken; if (idToken == null) throw Exception('Failed to get Google ID token'); final res = await _api.post( ApiEndpoints.googleLogin, body: {'id_token': idToken}, requiresAuth: false, ); final token = res['token']; if (token == null) throw Exception('Token missing from response'); final serverEmail = (res['email'] as String?) ?? account.email; final displayName = (res['username'] as String?) ?? account.displayName ?? serverEmail; AuthGuard.setGuest(false); await TokenStorage.saveToken(token.toString(), serverEmail); final prefs = await SharedPreferences.getInstance(); await prefs.setString('current_email', serverEmail); await prefs.setString('email', serverEmail); final perKey = 'display_name_$serverEmail'; if ((prefs.getString(perKey) ?? '').isEmpty) { await prefs.setString(perKey, displayName); } if (res['phone_number'] != null) await prefs.setString('phone_number', res['phone_number'].toString()); // Save profile photo from Google login response final rawPhoto = res['profile_photo']?.toString() ?? ''; if (rawPhoto.isNotEmpty) { final photoUrl = rawPhoto.startsWith('http') ? rawPhoto : 'https://em.eventifyplus.com$rawPhoto'; await prefs.setString('profileImage_$serverEmail', photoUrl); await prefs.setString('profileImage', photoUrl); } // Save Eventify ID final eventifyId = res['eventify_id']?.toString() ?? ''; if (eventifyId.isNotEmpty) await prefs.setString('eventify_id', eventifyId); PostHogService.instance.identify(serverEmail, properties: { 'username': displayName, 'login_method': 'google', }); PostHogService.instance.capture('user_logged_in'); return UserModel.fromJson(res); } catch (e) { if (kDebugMode) debugPrint('AuthService.googleLogin error: $e'); rethrow; } } /// FORGOT PASSWORD → backend sends reset instructions by email. /// Frontend never leaks whether the email is registered — same UX on success and 404. Future forgotPassword(String email) async { await _api.post( ApiEndpoints.forgotPassword, body: {'email': email}, requiresAuth: false, ); } /// Logout – clear auth token and current_email (keep per-account display_name entries so they persist) Future logout() async { try { final prefs = await SharedPreferences.getInstance(); // clear token storage (should delete token + auth headers) await TokenStorage.clear(); // remove current_email marker so no previous account remains current await prefs.remove('current_email'); // Also remove canonical 'email' pointing to current user await prefs.remove('email'); // Do not delete display_name_ entries — they are per-account and should remain on device. PostHogService.instance.capture('user_logged_out'); PostHogService.instance.reset(); } catch (e) { if (kDebugMode) debugPrint('AuthService.logout warning: $e'); } } }