// lib/services/firebase_service.dart import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:kzeduca_app/services/user_state_service.dart'; import 'package:kzeduca_app/models/app_models.dart' as app_models; import 'package:flutter/foundation.dart'; import '../models/budget.dart'; import '../models/transaction.dart' as app; import 'package:google_sign_in/google_sign_in.dart'; // <--- 🌟 NOVO: Importação do Google Sign-In class FirebaseService { final FirebaseAuth _auth = FirebaseAuth.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance; final UserStateService _userStateService; // 🌟 NOVO: Inicializa o GoogleSignIn final GoogleSignIn _googleSignIn = GoogleSignIn(); final String _appId; FirebaseService(this._userStateService, {String? appId}) : _appId = appId ?? 'default-kzeduca-app'; FirebaseAuth get auth => _auth; FirebaseFirestore get firestore => _firestore; // --- Métodos de Autenticação --- Future initializeAuth(String? initialAuthToken) async { if (_auth.currentUser != null) { if (kDebugMode) { print('Usuário já autenticado, ignorando a inicialização.'); } return; } try { if (initialAuthToken != null) { // ✅ NOVO: Usar o token personalizado para autenticação se ele existir await _auth.signInWithCustomToken(initialAuthToken); if (kDebugMode) { print('Autenticado com token personalizado.'); } } else { // ✅ FALLBACK: Se o token não existir, autentica anonimamente await _auth.signInAnonymously(); if (kDebugMode) { print('Autenticado anonimamente.'); } } } on FirebaseAuthException catch (e) { if (e.code == 'operation-not-allowed' || e.code == 'admin-restricted-operation') { if (kDebugMode) { print("Login anônimo desabilitado, o usuário não será autenticado anonimamente."); } return; } if (kDebugMode) { print('Erro de autenticação inicial: ${e.code}'); } rethrow; } catch (e) { if (kDebugMode) { print('Erro inesperado durante a autenticação inicial: $e'); } rethrow; } } // ----------------------------------------------------------- // 🌟 NOVO MÉTODO: LOGIN COM GOOGLE // ----------------------------------------------------------- Future signInWithGoogle() async { try { // 1. Inicia o fluxo de autenticação do Google final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); if (googleUser == null) { // O usuário cancelou o processo throw FirebaseAuthException( code: 'google-login-cancelled', message: 'O login com o Google foi cancelado pelo usuário.', ); } // 2. Obtém a autenticação (token) final GoogleSignInAuthentication googleAuth = await googleUser.authentication; // 3. Cria a credencial do Firebase final AuthCredential credential = GoogleAuthProvider.credential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken, ); // 4. Faz login no Firebase final userCredential = await _auth.signInWithCredential(credential); final firebaseUser = userCredential.user; if (firebaseUser != null) { // 5. Garante a existência/atualização do perfil no Firestore // Usa o nome do Google como nome de utilizador padrão. final username = googleUser.displayName ?? 'Utilizador Google'; final newUserProfileData = { 'uid': firebaseUser.uid, 'username': username, 'email': firebaseUser.email, 'lastSignInAt': FieldValue.serverTimestamp(), // Se for um novo utilizador, estes campos serão criados (merge: true) 'currentBalance': 0.0, 'points': 0, 'badges': [], 'createdAt': FieldValue.serverTimestamp(), }; // Usa updateUserProfile com merge: true para criar ou atualizar de forma segura await updateUserProfile( uid: firebaseUser.uid, data: newUserProfileData, ); await _userStateService.fetchUserData(firebaseUser.uid); } } on FirebaseAuthException catch (e) { if (kDebugMode) { print('Erro de autenticação do Google: ${e.code}'); } rethrow; } catch (e) { if (kDebugMode) { print('Erro inesperado no Google Sign-In: $e'); } rethrow; } } // ----------------------------------------------------------- Future registerWithEmailAndPassword(String email, String password, String username) async { try { final userCredential = await _auth.createUserWithEmailAndPassword( email: email, password: password, ); final firebaseUser = userCredential.user; if (firebaseUser != null) { final newUserProfileData = { 'uid': firebaseUser.uid, 'username': username, 'email': firebaseUser.email, 'currentBalance': 0.0, 'points': 0, 'badges': [], 'createdAt': FieldValue.serverTimestamp(), }; await _firestore .collection('artifacts') .doc(_appId) .collection('users') .doc(firebaseUser.uid) .set(newUserProfileData); await _userStateService.fetchUserData(firebaseUser.uid); } } on FirebaseAuthException catch (e) { if (kDebugMode) { print('Erro de autenticação no registro: ${e.code}'); } rethrow; } } Future signInWithEmailAndPassword(String email, String password) async { try { final userCredential = await _auth.signInWithEmailAndPassword( email: email, password: password, ); final firebaseUser = userCredential.user; if (firebaseUser != null) { final userProfileRef = _firestore.collection('artifacts').doc(_appId).collection('users').doc(firebaseUser.uid); final userProfileSnapshot = await userProfileRef.get(); if (!userProfileSnapshot.exists) { if (kDebugMode) { print('Documento de perfil não encontrado. Criando um novo.'); } // Usa updateUserProfile para garantir que a lógica de criação/atualização seja consistente await updateUserProfile( uid: firebaseUser.uid, data: { 'uid': firebaseUser.uid, 'email': firebaseUser.email, 'username': 'Utilizador', // Nome padrão 'currentBalance': 0.0, 'points': 0, 'badges': [], 'createdAt': FieldValue.serverTimestamp(), }, ); } await _userStateService.fetchUserData(firebaseUser.uid); } } on FirebaseAuthException catch (e) { if (kDebugMode) { print('Erro de autenticação no login: ${e.code}'); } rethrow; } } Future signOut() async { await _auth.signOut(); // 🌟 NOVO: Desconecta também do Google Sign-In para evitar login automático na próxima vez await _googleSignIn.signOut(); } String? get uid => _auth.currentUser?.uid; CollectionReference _getUserCollection() { return _firestore.collection('artifacts').doc(_appId).collection('users'); } CollectionReference get _transactionsCollection { if (uid == null) { throw Exception('User not logged in'); } return _getUserCollection().doc(uid).collection('transactions'); } CollectionReference get _budgetsCollection { if (uid == null) { throw Exception('User not logged in'); } return _getUserCollection().doc(uid).collection('budgets'); } // ✅ NOVO MÉTODO: Adicionado para resolver o erro 'update' de documento inexistente. // Utiliza 'set' com 'merge: true' para criar ou atualizar o perfil. Future updateUserProfile({ required String uid, required Map data, }) async { try { await _firestore .collection('artifacts') .doc(_appId) .collection('users') .doc(uid) .set( data, SetOptions(merge: true), ); if (kDebugMode) { print('Perfil do utilizador $uid atualizado com sucesso.'); } } on FirebaseException catch (e) { if (kDebugMode) { print('Erro no Firebase ao atualizar o perfil: ${e.message}'); } rethrow; // Rejeita o erro para ser capturado no try-catch da UI. } catch (e) { if (kDebugMode) { print('Erro desconhecido ao atualizar o perfil: $e'); } rethrow; } } Future getUserProfile(String uid) async { return await _firestore.collection('artifacts').doc(_appId).collection('users').doc(uid).get(); } // NOTE: Este método é redundante com `updateUserProfile` e não é usado acima, mas mantido por integridade. Future createUserProfile(String uid, String email, String username) async { await _firestore .collection('artifacts') .doc(_appId) .collection('users') .doc(uid) .set({ 'uid': uid, 'email': email, 'username': username, 'createdAt': FieldValue.serverTimestamp(), 'points': 0, 'badges': [], }); } Stream streamUserProfile(String uid) { return _firestore.collection('artifacts').doc(_appId).collection('users').doc(uid).snapshots(); } // --- Métodos de Transações e Orçamentos --- Future> getTransactions() async { final snapshot = await _transactionsCollection.get(); return snapshot.docs.map((doc) => app.Transaction.fromMap(doc.data() as Map)).toList(); } Future addTransaction(app.Transaction transaction) async { await _transactionsCollection.add(transaction.toMap()); } Future deleteTransaction(String transactionId) async { await _transactionsCollection.doc(transactionId).delete(); } Future> getBudgets() async { final snapshot = await _budgetsCollection.get(); return snapshot.docs.map((doc) => Budget.fromFirestore(doc)).toList(); } Future addBudget(Budget budget) async { await _budgetsCollection.add(budget.toMap()); } Future deleteBudget(String budgetId) async { await _budgetsCollection.doc(budgetId).delete(); } // --- Métodos de Simulações de Poupança --- Future saveSimulation(String userId, Map simulationData) async { await _getUserCollection().doc(userId).collection('simulations').add(simulationData); } Stream streamSavedSimulations(String userId) { return _getUserCollection().doc(userId).collection('simulations').snapshots(); } // --- Métodos de Desafios --- // Obtém todos os desafios públicos para exibição Future> getAllChallenges() async { final snapshot = await _firestore.collection('artifacts').doc(_appId).collection('public').doc('data').collection('challenges').get(); return snapshot.docs.map((doc) => app_models.FinancialChallenge.fromFirestore(doc)).toList(); } // Fluxo em tempo real para desafios públicos Stream streamPublicChallenges() { return _firestore.collection('artifacts').doc(_appId).collection('public').doc('data').collection('challenges').snapshots(); } // Fluxo em tempo real para os desafios aceites pelo usuário Stream streamUserChallenges(String userId) { return _getUserCollection().doc(userId).collection('userChallenges').snapshots(); } Future acceptChallenge(String userId, Map challengeData) async { await _getUserCollection().doc(userId).collection('userChallenges').add(challengeData); } Future updateChallengeProgress(String userId, String challengeId, int progress) async { await _getUserCollection().doc(userId).collection('userChallenges').doc(challengeId).update({ 'progresso': progress, 'lastUpdated': FieldValue.serverTimestamp(), }); } // --- Métodos de Aulas --- Stream streamPublicLessons() { return _firestore.collection('artifacts').doc(_appId).collection('public').doc('data').collection('lessons').snapshots(); } Stream streamUserCompletedLessons(String userId) { return _getUserCollection().doc(userId).collection('userLessons').snapshots(); } Future markLessonAsComplete(String userId, String lessonId) async { await _getUserCollection().doc(userId).collection('userLessons').doc(lessonId).set({ 'lessonId': lessonId, 'completedAt': FieldValue.serverTimestamp(), }); } // --- Métodos de Quiz --- Stream streamQuizQuestions() { return _firestore.collection('artifacts').doc(_appId).collection('public').doc('data').collection('quizQuestions').snapshots(); } Stream streamUserQuizResults(String userId) { return _getUserCollection().doc(userId).collection('userQuizResults').snapshots(); } Future saveQuizResult(String userId, Map quizResultData) async { await _getUserCollection().doc(userId).collection('userQuizResults').add(quizResultData); } }