using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.Users; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; namespace Managing.Application.Accounts; public class AccountService : IAccountService { private readonly IAccountRepository _accountRepository; private readonly IExchangeService _exchangeService; private readonly IEvmManager _evmManager; private readonly ICacheService _cacheService; private readonly IWeb3ProxyService _web3ProxyService; private readonly IUserRepository _userRepository; private readonly ILogger _logger; public AccountService( IAccountRepository accountRepository, ILogger logger, IExchangeService exchangeService, IEvmManager evmManager, ICacheService cacheService, IWeb3ProxyService web3ProxyService, IUserRepository userRepository) { _accountRepository = accountRepository; _logger = logger; _exchangeService = exchangeService; _evmManager = evmManager; _cacheService = cacheService; _web3ProxyService = web3ProxyService; _userRepository = userRepository; } public async Task CreateAccount(User user, Account account) { var a = await _accountRepository.GetAccountByNameAsync(account.Name); if (a != null) { throw new Exception($"Account {account.Name} alreary exist"); } else { account.User = user; if (account.Exchange == TradingExchanges.Evm && account.Type == AccountType.Trader) { var keys = _evmManager.GenerateAddress(); account.Key = keys.Key; account.Secret = keys.Secret; } else if (account.Exchange == TradingExchanges.Evm && account.Type == AccountType.Privy) { if (string.IsNullOrEmpty(account.Key)) { // No key provided, create new privy embedded wallet. // TODO : Fix it to create privy wallet var privyClient = await _evmManager.CreatePrivyWallet(); account.Key = privyClient.Address; account.Secret = privyClient.Id; } else { account.Key = account.Key; // Address account.Secret = account.Secret; // Privy wallet id } } else { account.Key = account.Key; account.Secret = account.Secret; } await _accountRepository.InsertAccountAsync(account); } return account; } public bool DeleteAccount(User user, string name) { try { _accountRepository.DeleteAccountByName(name); return true; } catch (Exception ex) { _logger.LogError(ex.Message); return false; } } public async Task GetAccount(string name, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByNameAsync(name); if (account == null) { throw new ArgumentException($"Account '{name}' not found"); } await ManagePropertiesAsync(hideSecrets, getBalance, account); if (account.User == null && account.User != null) { account.User = await _userRepository.GetUserByNameAsync(account.User.Name); } return account; } public async Task GetAccountByKey(string key, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByKeyAsync(key); await ManagePropertiesAsync(hideSecrets, getBalance, account); return account; } public async Task GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance) { var account = await _accountRepository.GetAccountByNameAsync(name); await ManagePropertiesAsync(hideSecrets, getBalance, account); return account; } public async Task GetAccountByAccountName(string accountName, bool hideSecrets = true, bool getBalance = false) { var account = await _accountRepository.GetAccountByNameAsync(accountName); if (account != null) { await ManagePropertiesAsync(hideSecrets, getBalance, account); if (account.User != null) { account.User = await _userRepository.GetUserByNameAsync(account.User.Name); } } return account; } public async Task> GetAccounts(bool hideSecrets, bool getBalance) { return await GetAccountsAsync(hideSecrets, getBalance); } public async Task> GetAccountsAsync(bool hideSecrets, bool getBalance) { var result = await _accountRepository.GetAccountsAsync(); var accounts = new List(); foreach (var account in result) { await ManagePropertiesAsync(hideSecrets, getBalance, account); accounts.Add(account); } return accounts; } public IEnumerable GetAccountsByUser(User user, bool hideSecrets = true) { return GetAccountsByUserAsync(user, hideSecrets).Result; } public async Task> GetAccountsByUserAsync(User user, bool hideSecrets = true, bool getBalance = false) { var cacheKey = $"user-account-{user.Name}"; // For now, we'll get fresh data since caching async operations requires more complex logic // This can be optimized later with proper async caching return await GetAccountsAsync(user, hideSecrets, getBalance); } private async Task> GetAccountsAsync(User user, bool hideSecrets, bool getBalance) { var result = await _accountRepository.GetAccountsAsync(); var accounts = new List(); foreach (var account in result.Where(a => a.User.Name == user.Name)) { await ManagePropertiesAsync(hideSecrets, getBalance, account); accounts.Add(account); } return accounts; } public IEnumerable GetAccountsBalancesByUser(User user, bool hideSecrets) { return GetAccountsBalancesByUserAsync(user, hideSecrets).Result; } public async Task> GetAccountsBalancesByUserAsync(User user, bool hideSecrets) { var cacheKey = $"user-account-balance-{user.Name}"; // For now, get fresh data since caching async operations requires more complex logic // This can be optimized later with proper async caching return await GetAccountsAsync(user, hideSecrets, true); } public async Task GetGmxClaimableSummaryAsync(User user, string accountName) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } try { // Call the Web3ProxyService to get GMX claimable summary var infrastructureResponse = await _web3ProxyService.GetGmxClaimableSummaryAsync(account.Key); // Map from infrastructure model to domain model return infrastructureResponse; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error getting GMX claimable summary for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to get GMX claimable summary: {ex.Message}", ex); } } public async Task SwapGmxTokensAsync(User user, string accountName, Ticker fromTicker, Ticker toTicker, double amount, string orderType = "market", double? triggerRatio = null, double allowedSlippage = 0.5) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } try { // Call the Web3ProxyService to swap GMX tokens var swapInfos = await _web3ProxyService.SwapGmxTokensAsync( account.Key, fromTicker, toTicker, amount, orderType, triggerRatio, allowedSlippage ); return swapInfos; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error swapping GMX tokens for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to swap GMX tokens: {ex.Message}", ex); } } public async Task SendTokenAsync(User user, string accountName, string recipientAddress, Ticker ticker, decimal amount, int? chainId = null) { // Get the account for the user var account = await GetAccountByUser(user, accountName, true, false); if (account == null) { throw new ArgumentException($"Account '{accountName}' not found for user '{user.Name}'"); } // Ensure the account has a valid address/key if (string.IsNullOrEmpty(account.Key)) { throw new ArgumentException($"Account '{accountName}' does not have a valid address"); } // Validate recipient address if (string.IsNullOrEmpty(recipientAddress)) { throw new ArgumentException("Recipient address is required"); } // Validate amount if (amount <= 0) { throw new ArgumentException("Amount must be greater than zero"); } try { // Call the Web3ProxyService to send tokens var swapInfos = await _web3ProxyService.SendTokenAsync( account.Key, recipientAddress, ticker, amount, chainId ); return swapInfos; } catch (Exception ex) when (!(ex is ArgumentException || ex is InvalidOperationException)) { _logger.LogError(ex, "Error sending tokens for account {AccountName} and user {UserName}", accountName, user.Name); throw new InvalidOperationException($"Failed to send tokens: {ex.Message}", ex); } } public async Task> GetExchangeApprovalStatusAsync(User user) { var accounts = await GetAccountsByUserAsync(user, hideSecrets: true, getBalance: false); var exchangeStatuses = new List(); foreach (var account in accounts) { exchangeStatuses.Add(new ExchangeApprovalStatus { Exchange = TradingExchanges.GmxV2, IsApproved = account.IsGmxInitialized }); } // Future: Add other exchanges here when supported // e.g.: // var hasEvmInitialized = accounts.Any(account => // account.Exchange == TradingExchanges.Evm && account.IsGmxInitialized); // exchangeStatuses.Add(new ExchangeApprovalStatus // { // Exchange = TradingExchanges.Evm, // IsApproved = hasEvmInitialized // }); return exchangeStatuses; } private async Task ManagePropertiesAsync(bool hideSecrets, bool getBalance, Account account) { if (account != null) { if (getBalance) { try { account.Balances = await _exchangeService.GetBalances(account); } catch (Exception ex) { _logger.LogError(ex, $"Cannot get account {account.Name} balance"); } } if (hideSecrets) { account.Secret = ""; } } } }