using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Users;
using Microsoft.Extensions.Logging;

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

    public AccountService(
        IAccountRepository accountRepository,
        ILogger<AccountService> logger,
        IExchangeService exchangeService,
        IEvmManager evmManager,
        ICacheService cacheService)
    {
        _accountRepository = accountRepository;
        _logger = logger;
        _exchangeService = exchangeService;
        _evmManager = evmManager;
        _cacheService = cacheService;
    }

    public async Task<Account> CreateAccount(User user, Account request)
    {
        var account = await _accountRepository.GetAccountByNameAsync(request.Name);

        if (account != null)
        {
            throw new Exception($"Account {request.Name} alreary exist");
        }
        else
        {
            request.User = user;

            if (request.Exchange == Enums.TradingExchanges.Evm
                && request.Type == Enums.AccountType.Trader)
            {
                var keys = _evmManager.GenerateAddress();
                request.Key = keys.Key;
                request.Secret = keys.Secret;
            }
            else if (request.Exchange == Enums.TradingExchanges.Evm
                     && request.Type == Enums.AccountType.Privy)
            {
                if (string.IsNullOrEmpty(request.Key))
                {
                    // No key provided, create new privy embedded wallet.
                    // TODO : Fix it to create privy wallet
                    var privyClient = await _evmManager.CreatePrivyWallet();
                    request.Key = privyClient.Address;
                    request.Secret = privyClient.Id;
                }
                else
                {
                    request.Key = request.Key; // Address
                    request.Secret = request.Secret; // Privy wallet id
                }
            }
            else
            {
                request.Key = request.Key;
                request.Secret = request.Secret;
            }

            await _accountRepository.InsertAccountAsync(request);
        }

        return request;
    }

    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);
        ManageProperties(hideSecrets, getBalance, account);

        return account;
    }

    public async Task<Account> GetAccountByKey(string key, bool hideSecrets, bool getBalance)
    {
        var account = await _accountRepository.GetAccountByKeyAsync(key);
        ManageProperties(hideSecrets, getBalance, account);

        return account;
    }

    public async Task<Account> GetAccountByUser(User user, string name, bool hideSecrets, bool getBalance)
    {
        var account = await _accountRepository.GetAccountByNameAsync(name);
        ManageProperties(hideSecrets, getBalance, account);

        return account;
    }

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

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

        return accounts;
    }

    public IEnumerable<Account> GetAccountsByUser(User user, bool hideSecrets = true)
    {
        var cacheKey = $"user-account-{user.Name}";

        return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
            TimeSpan.FromMinutes(5));
    }

    private IEnumerable<Account> GetAccounts(User user, bool hideSecrets, bool getBalance)
    {
        var result = _accountRepository.GetAccounts();
        var accounts = new List<Account>();

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

        return accounts;
    }

    public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
    {
        var cacheKey = $"user-account-balance-{user.Name}";
        var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); },
            TimeSpan.FromHours(3));

        return accounts;
    }

    private void ManageProperties(bool hideSecrets, bool getBalance, Account account)
    {
        if (account != null)
        {
            if (getBalance)
            {
                try
                {
                    account.Balances = _exchangeService.GetBalances(account).Result;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Cannot get account {account.Name} balance");
                }
            }

            if (hideSecrets)
            {
                account.Secret = "";
            }
        }
    }
}