import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/auth_session.dart'; import 'local_seed_repository.dart'; abstract class AuthRepository { Future restoreSession(); Future signIn({required String email, required String password}); Future signUp({required String name, required String email, required String password}); Future signOut(); } class LocalAuthRepository implements AuthRepository { const LocalAuthRepository(); static const String _tokenKey = 'auth_token'; static const String _emailKey = 'auth_email'; static const String _nameKey = 'auth_name'; static const String _sessionKey = 'auth_session_json'; static const String _usersKey = 'auth_users_json'; @override Future restoreSession() async { final prefs = await SharedPreferences.getInstance(); final sessionJson = prefs.getString(_sessionKey); if (sessionJson != null && sessionJson.trim().isNotEmpty) { final decoded = jsonDecode(sessionJson); if (decoded is Map) { final token = decoded['token']?.toString(); final email = decoded['email']?.toString(); final name = decoded['displayName']?.toString(); if (token != null && email != null && token.isNotEmpty && email.isNotEmpty) { return AuthSession(token: token, email: email, displayName: name ?? email); } } } final token = prefs.getString(_tokenKey); final email = prefs.getString(_emailKey); final name = prefs.getString(_nameKey); if (token == null || email == null) { return null; } return AuthSession(token: token, email: email, displayName: name ?? email); } @override Future signIn({required String email, required String password}) { return _authenticate( endpoint: '/auth/login', body: {'email': email, 'password': password}, ); } @override Future signUp({required String name, required String email, required String password}) { return _authenticate( endpoint: '/auth/register', body: {'name': name, 'email': email, 'password': password}, ); } Future _authenticate({required String endpoint, required Map body}) async { final email = body['email']?.toString().trim(); final password = body['password']?.toString(); if (email == null || password == null || email.isEmpty) { throw AuthException('Ingresa correo y contraseña válidos.'); } final seedData = await LocalSeedRepository.instance.load(); final profile = seedData.profileForCredentials(email, password); if (profile != null) { final session = AuthSession( token: 'local-${profile.email}', email: profile.email, displayName: profile.name, ); await _persistSession(session); return session; } final localUsers = await _loadLocalUsers(); final normalizedEmail = email.toLowerCase(); final localMatch = localUsers.where((user) { final userEmail = user['email']?.toString().trim().toLowerCase(); final userPassword = user['password']?.toString(); return userEmail == normalizedEmail && userPassword == password; }).toList(growable: false); if (localMatch.isNotEmpty) { final user = localMatch.first; final displayName = user['name']?.toString().trim(); final session = AuthSession( token: 'local-${email.toLowerCase()}', email: email, displayName: displayName == null || displayName.isEmpty ? email : displayName, ); await _persistSession(session); return session; } if (endpoint == '/auth/register') { final displayName = body['name']?.toString().trim(); final alreadyInSeed = seedData.profileForCredentials(email, password) != null || seedData.demoProfiles.any((profile) => profile.email.trim().toLowerCase() == normalizedEmail); final alreadyInLocal = localUsers.any((user) => user['email']?.toString().trim().toLowerCase() == normalizedEmail); if (alreadyInSeed || alreadyInLocal) { throw AuthException('Este correo ya está registrado.'); } final createdUser = { 'name': displayName == null || displayName.isEmpty ? email : displayName, 'email': email, 'password': password, 'createdAt': DateTime.now().toIso8601String(), }; await _saveLocalUsers(>[...localUsers, createdUser]); final session = AuthSession( token: 'local-$email', email: email, displayName: displayName == null || displayName.isEmpty ? email : displayName, ); await _persistSession(session); return session; } throw AuthException('Usuario o contraseña no encontrados en los perfiles locales.'); } Future _persistSession(AuthSession session) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_tokenKey, session.token); await prefs.setString(_emailKey, session.email); await prefs.setString(_nameKey, session.displayName); await prefs.setString( _sessionKey, jsonEncode( { 'token': session.token, 'email': session.email, 'displayName': session.displayName, }, ), ); } Future>> _loadLocalUsers() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString(_usersKey); if (raw == null || raw.trim().isEmpty) { return >[]; } final decoded = jsonDecode(raw); if (decoded is! List) { return >[]; } return decoded.whereType>().toList(growable: false); } Future _saveLocalUsers(List> users) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_usersKey, jsonEncode(users)); } @override Future signOut() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_tokenKey); await prefs.remove(_emailKey); await prefs.remove(_nameKey); await prefs.remove(_sessionKey); } } class AuthException implements Exception { AuthException(this.message); final String message; @override String toString() => message; }