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<AccountService> _logger;

    public AccountService(
        IAccountRepository accountRepository,
        ILogger<AccountService> 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<Account> 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<Account> 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<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance)
    {
        var account = await _accountRepository.GetAccountByKeyAsync(key);
        await ManagePropertiesAsync(hideSecrets, getBalance, account);

        return account;
    }

    public async Task<Account> 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<Account> 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<IEnumerable<Account>> GetAccounts(bool hideSecrets, bool getBalance)
    {
        return await GetAccountsAsync(hideSecrets, getBalance);
    }

    public async Task<IEnumerable<Account>> GetAccountsAsync(bool hideSecrets, bool getBalance)
    {
        var result = await _accountRepository.GetAccountsAsync();
        var accounts = new List<Account>();

        foreach (var account in result)
        {
            await ManagePropertiesAsync(hideSecrets, getBalance, account);
            accounts.Add(account);
        }

        return accounts;
    }

    public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
    {
        return GetAccountsByUserAsync(user, hideSecrets).Result;
    }

    public async Task<IEnumerable<Account>> 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<IEnumerable<Account>> GetAccountsAsync(User user, bool hideSecrets, bool getBalance)
    {
        var result = await _accountRepository.GetAccountsAsync();
        var accounts = new List<Account>();

        foreach (var account in result.Where(a => a.User.Name == user.Name))
        {
            await ManagePropertiesAsync(hideSecrets, getBalance, account);
            accounts.Add(account);
        }

        return accounts;
    }

    public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
    {
        return GetAccountsBalancesByUserAsync(user, hideSecrets).Result;
    }

    public async Task<IEnumerable<Account>> 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<GmxClaimableSummary> 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<SwapInfos> 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<SwapInfos> 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);
        }
    }

    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 = "";
            }
        }
    }
}