package com.rosetta.messenger.providers import android.content.Context import androidx.compose.runtime.* import com.rosetta.messenger.crypto.CryptoManager import com.rosetta.messenger.database.DatabaseService import com.rosetta.messenger.database.DecryptedAccountData import com.rosetta.messenger.network.ProtocolManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Auth state management - matches React Native architecture */ sealed class AuthStatus { object Loading : AuthStatus() object Unauthenticated : AuthStatus() data class Authenticated(val account: DecryptedAccountData) : AuthStatus() data class Locked(val publicKey: String) : AuthStatus() } data class AuthStateData( val status: AuthStatus = AuthStatus.Loading, val hasExistingAccounts: Boolean = false, val availableAccounts: List = emptyList() ) class AuthStateManager( private val context: Context, private val scope: CoroutineScope ) { private val databaseService = DatabaseService.getInstance(context) private val _state = MutableStateFlow(AuthStateData()) val state: StateFlow = _state.asStateFlow() private var currentDecryptedAccount: DecryptedAccountData? = null // 🚀 ОПТИМИЗАЦИЯ: Кэш списка аккаунтов для UI private var accountsCache: List? = null private var lastAccountsLoadTime = 0L private val accountsCacheTTL = 5000L // 5 секунд companion object { private const val TAG = "AuthStateManager" } init { scope.launch { loadAccounts() checkAuthStatus() } } private suspend fun loadAccounts() = withContext(Dispatchers.IO) { try { // 🚀 ОПТИМИЗАЦИЯ: Используем кэш если он свежий val currentTime = System.currentTimeMillis() if (accountsCache != null && (currentTime - lastAccountsLoadTime) < accountsCacheTTL) { _state.update { it.copy( hasExistingAccounts = accountsCache!!.isNotEmpty(), availableAccounts = accountsCache!! )} return@withContext } val accounts = databaseService.getAllEncryptedAccounts() val hasAccounts = accounts.isNotEmpty() val accountKeys = accounts.map { it.publicKey } // Обновляем кэш accountsCache = accountKeys lastAccountsLoadTime = currentTime _state.update { it.copy( hasExistingAccounts = hasAccounts, availableAccounts = accountKeys )} } catch (e: Exception) { } } private suspend fun checkAuthStatus() { try { val hasAccounts = databaseService.hasAccounts() if (!hasAccounts) { _state.update { it.copy( status = AuthStatus.Unauthenticated )} } else { _state.update { it.copy( status = AuthStatus.Unauthenticated )} } } catch (e: Exception) { _state.update { it.copy( status = AuthStatus.Unauthenticated )} } } /** * Create new account from seed phrase * Matches createAccountFromSeedPhrase from React Native * 🚀 ОПТИМИЗАЦИЯ: Dispatchers.Default для CPU-интенсивной криптографии */ suspend fun createAccount( seedPhrase: List, password: String ): Result = withContext(Dispatchers.Default) { try { // Step 1: Generate key pair from seed phrase (using BIP39) val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase) // Step 2: Generate private key hash for protocol val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey) // Step 3: Encrypt private key with password val encryptedPrivateKey = CryptoManager.encryptWithPassword( keyPair.privateKey, password ) // Step 4: Encrypt seed phrase with password val encryptedSeedPhrase = CryptoManager.encryptWithPassword( seedPhrase.joinToString(" "), password ) // Step 5: Save to database val saved = withContext(Dispatchers.IO) { databaseService.saveEncryptedAccount( publicKey = keyPair.publicKey, privateKeyEncrypted = encryptedPrivateKey, seedPhraseEncrypted = encryptedSeedPhrase ) } if (!saved) { return@withContext Result.failure(Exception("Failed to save account to database")) } // Step 6: Create decrypted account object val decryptedAccount = DecryptedAccountData( publicKey = keyPair.publicKey, privateKey = keyPair.privateKey, privateKeyHash = privateKeyHash, seedPhrase = seedPhrase ) // Step 7: Update state and reload accounts currentDecryptedAccount = decryptedAccount _state.update { it.copy( status = AuthStatus.Authenticated(decryptedAccount) )} loadAccounts() // Step 8: Connect and authenticate with protocol ProtocolManager.connect() // Give WebSocket time to connect before authenticating kotlinx.coroutines.delay(500) ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash) Result.success(decryptedAccount) } catch (e: Exception) { Result.failure(e) } } /** * Unlock account with password * Matches loginWithPassword from React Native */ suspend fun unlock( publicKey: String, password: String ): Result = withContext(Dispatchers.Default) { try { // Decrypt account from database val decryptedAccount = withContext(Dispatchers.IO) { databaseService.decryptAccount(publicKey, password) } if (decryptedAccount == null) { return@withContext Result.failure(Exception("Invalid password or account not found")) } // Update last used timestamp withContext(Dispatchers.IO) { databaseService.updateLastUsed(publicKey) } // Update state currentDecryptedAccount = decryptedAccount _state.update { it.copy( status = AuthStatus.Authenticated(decryptedAccount) )} // Connect and authenticate with protocol ProtocolManager.connect() // Give WebSocket time to connect before authenticating kotlinx.coroutines.delay(500) ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash) Result.success(decryptedAccount) } catch (e: Exception) { Result.failure(e) } } /** * Logout - clears decrypted account from memory */ fun logout() { currentDecryptedAccount = null _state.update { it.copy( status = AuthStatus.Unauthenticated )} } /** * Delete account from database */ suspend fun deleteAccount(publicKey: String): Result = withContext(Dispatchers.IO) { try { val success = databaseService.deleteAccount(publicKey) if (!success) { return@withContext Result.failure(Exception("Failed to delete account")) } // If deleting current account, logout if (currentDecryptedAccount?.publicKey == publicKey) { withContext(Dispatchers.Main) { logout() } } loadAccounts() Result.success(Unit) } catch (e: Exception) { Result.failure(e) } } /** * Get current decrypted account (if authenticated) */ fun getCurrentAccount(): DecryptedAccountData? = currentDecryptedAccount } @Composable fun rememberAuthState(context: Context): AuthStateManager { val scope = rememberCoroutineScope() return remember(context) { AuthStateManager(context, scope) } } @Composable fun ProvideAuthState( authState: AuthStateManager, content: @Composable (AuthStateData) -> Unit ) { val state by authState.state.collectAsState() content(state) }