using System.Net.Http.Json;
using System.Numerics;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Common;
using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Evm;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Models.Proxy;
using Managing.Infrastructure.Evm.Referentials;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Services.Gmx;
using NBitcoin;
using Nethereum.Contracts;
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
using Nethereum.HdWallet;
using Nethereum.Hex.HexTypes;
using Nethereum.Signer;
using Nethereum.Web3;
using static Managing.Common.Enums;
using BalanceOfFunction = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfFunction;
using BalanceOfOutputDTO = Nethereum.Contracts.Standards.ERC20.ContractDefinition.BalanceOfOutputDTO;
using Chain = Managing.Domain.Evm.Chain;
using TransferEventDTO = Nethereum.Contracts.Standards.ERC721.ContractDefinition.TransferEventDTO;

namespace Managing.Infrastructure.Evm;

public class EvmManager : IEvmManager
{
    private readonly Web3 _web3;
    private readonly HttpClient _httpClient;
    private readonly string _password = "!StrongPassword94";
    private readonly IEnumerable<ISubgraphPrices> _subgraphs;
    private readonly GmxV2Service _gmxV2Service;
    private readonly IWeb3ProxyService _web3ProxyService;
    private readonly ICacheService _cacheService;

    public EvmManager(IEnumerable<ISubgraphPrices> subgraphs,
        IWeb3ProxyService web3ProxyService, ICacheService cacheService)
    {
        var defaultChain = ChainService.GetEthereum();
        _web3 = new Web3(defaultChain.RpcUrl);
        _httpClient = new HttpClient();
        _subgraphs = subgraphs;
        _web3ProxyService = web3ProxyService;
        _gmxV2Service = new GmxV2Service();
        _cacheService = cacheService;
    }

    public async Task<decimal> GetAddressBalance(string address)
    {
        var balance = await _web3.Eth.GetBalance.SendRequestAsync(address);
        var etherAmount = Web3.Convert.FromWei(balance.Value);
        return etherAmount;
    }

    public async Task<List<Holder>> GetContractHolders(string contractAddress, DateTime since)
    {
        var holders = new List<Holder>();
        var contract = _web3.Eth.ERC721.GetContractService(contractAddress);

        // Retrieve total supply to iterate over all token id generated within the contract
        var totalSupply = await contract.TotalSupplyQueryAsync();

        for (int tokenId = 1; tokenId < 10; tokenId++)
        {
            // Retrieve the owner of the nft
            var tokenOwner = await contract.OwnerOfQueryAsync(tokenId);

            // If holder already have an nft we get the holder
            // Otherwise we create a new holder
            var holder = holders.FirstOrDefault(h => h.HolderAddress == tokenOwner) ?? new Holder(tokenOwner);

            // Retrieve all events related to the owner on the contract address
            var nfts = await GetNftEvent(contractAddress, tokenOwner);

            // Get tokenId related event
            var nftEvent = nfts.FirstOrDefault(n => n.Event.TokenId == new BigInteger(tokenId));

            if (nftEvent != null)
            {
                // Retrieve the date of the nft event that occur in the blocknumber
                var blockDate = await GetBlockDate(nftEvent.Log.BlockNumber);
                var nft = new Nft(contractAddress, tokenId, blockDate);

                // Verify if the date of the holding is before the date passed from the parameters
                if (blockDate <= since)
                {
                    holder.AddNft(nft);

                    // If holder do not exist in the list we add it
                    if (!holders.Exists(h => h.HolderAddress == tokenOwner))
                    {
                        holders.Add(holder);
                    }
                }
                else
                {
                    Console.WriteLine($"TokenId #{tokenId} for owner {tokenOwner} date not in range ({blockDate:f})");
                }
            }
            else
            {
                Console.WriteLine($"Error when getting tokenId #{tokenId} for owner {tokenOwner}");
            }

            UpdateLine(tokenId, (int)totalSupply, holders.Count);
        }

        return holders;
    }

    public async Task<List<EventLog<TransferEventDTO>>>
        GetNftEvent(string contractAddress, string tokenOwner)
    {
        return await NftService.GetNftEvent(_web3, tokenOwner, contractAddress);
    }

    public async Task<DateTime> GetBlockDate(int blockNumber)
    {
        return await GetBlockDate(new HexBigInteger(blockNumber));
    }

    public async Task<DateTime> GetBlockDate(HexBigInteger blockNumber)
    {
        var block = await _web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(blockNumber);
        var date = DateHelpers.GetFromUnixTimestamp((int)block.Timestamp.Value);
        return date;
    }

    private void UpdateLine(int count, int total, int holders)
    {
        Console.WriteLine($"{count}/{total} - {(((decimal)count * 100m) / (decimal)total)} % - Holders : {holders}");
    }

    public string VerifySignature(string signature, string message)
    {
        var signer = new EthereumMessageSigner();
        var addressRecovered = signer.EncodeUTF8AndEcRecover(message, signature);
        return addressRecovered;
    }

    public string SignMessage(string message, string privateKey)
    {
        var signer = new EthereumMessageSigner();
        var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey));
        return signature;
    }

    public (string Key, string Secret) GenerateAddress()
    {
        var mnemo = new Mnemonic(Wordlist.English, WordCount.Twelve);
        var wallet = new Wallet(mnemo.ToString(), _password);
        var account = wallet.GetAccount(0);

        return (account.Address, mnemo.ToString());
    }

    public string GetAddressFromMnemo(string mnemo)
    {
        var wallet = new Wallet(mnemo, _password);
        return wallet.GetAccount(0).Address;
    }

    public async Task<EvmBalance> GetEtherBalance(Chain chain, string account)
    {
        var web3 = new Web3(chain.RpcUrl);
        var etherBalance = Web3.Convert.FromWei(await web3.Eth.GetBalance.SendRequestAsync(account));
        var lastCandle = await GetCandle(Ticker.ETH);

        return new EvmBalance()
        {
            Balance = etherBalance, Price = lastCandle.Close, TokenName = "ETH", Value = etherBalance * lastCandle.Close
        };
    }

    public async Task<List<EvmBalance>> GetAllBalances(Chain chain, string publicAddress)
    {
        var cacheKey = $"balances_{chain.Name}_{publicAddress.ToLowerInvariant()}";
        
        // Try to get from cache first
        var cachedBalances = _cacheService.GetValue<List<EvmBalance>>(cacheKey);
        if (cachedBalances != null)
        {
            return cachedBalances;
        }

        // If not in cache, fetch from blockchain
        var balances = new List<EvmBalance>();
        
        foreach (var ticker in TokenService.GetEligibleTickersForBalance())
        {
            try
            {
                var balance = await GetTokenBalance(chain.Name, ticker, publicAddress);
                if (balance != null && balance.Balance > 0)
                {
                    balances.Add(balance);
                }
            }
            catch (Exception ex)
            {
                SentrySdk.CaptureException(ex);
            }
        }

        var etherBalance = await GetEtherBalance(chain, publicAddress);
        etherBalance.Chain = chain;
        balances.Add(etherBalance);

        // Save to cache for 5 minutes
        _cacheService.SaveValue(cacheKey, balances, TimeSpan.FromMinutes(5));

        return balances;
    }

    public void ClearBalancesCache(Chain chain, string publicAddress)
    {
        var cacheKey = $"balances_{chain.Name}_{publicAddress.ToLowerInvariant()}";
        _cacheService.RemoveValue(cacheKey);
    }

    public async Task<EvmBalance> GetTokenBalance(string chainName, Ticker ticker, string publicAddress)
    {
        var chain = ChainService.GetChain(chainName);
        var web3 = new Web3(chain.RpcUrl);
        var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress };

        //Creating a new query handler
        var queryHandler = web3.Eth.GetContractQueryHandler<BalanceOfFunction>();

        var contractAddress = TokenService.GetContractAddress(ticker);

        if (contractAddress == Arbitrum.Address.Zero)
            return null;

        var balance = await queryHandler
            .QueryAsync<BigInteger>(contractAddress, balanceOfMessage)
            .ConfigureAwait(false);

        var lastCandle = await GetCandle(ticker);
        var tokenUsdPrice = lastCandle.Close;
        var tokenDecimal = TokenService.GetDecimal(ticker);
        var balanceFromWei = Web3.Convert.FromWei(balance, tokenDecimal);

        var evmBalance = new EvmBalance
        {
            TokenName = ticker.ToString(),
            Balance = balanceFromWei,
            TokenAddress = contractAddress,
            Value = tokenUsdPrice * balanceFromWei,
            Price = tokenUsdPrice,
            Chain = chain
        };

        return evmBalance;
    }

    public async Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize,
        string publicAddress)
    {
        var callList = new List<IMulticallInputOutput>();
        var startItem = (page * pageSize);
        var tokens = TokenService.GetTokens();
        var totaItemsToFetch = startItem + pageSize <= tokens.Count ? startItem + pageSize : tokens.Count + startItem;

        for (int i = startItem; i < totaItemsToFetch; i++)
        {
            var balanceOfMessage = new BalanceOfFunction() { Owner = publicAddress };
            var call = new MulticallInputOutput<BalanceOfFunction, BalanceOfOutputDTO>(balanceOfMessage,
                tokens[i].Address);
            callList.Add(call);
        }

        var evmTokens = new List<(EvmBalance Balance, GeckoToken GeckoToken)>();

        try
        {
            var web3 = new Web3(chain.RpcUrl);
            var geckoTokens = TokenService.GetGeckoTokens();

            await web3.Eth.GetMultiQueryHandler().MultiCallAsync(callList.ToArray());

            for (int i = startItem; i < totaItemsToFetch; i++)
            {
                var balance = ((MulticallInputOutput<BalanceOfFunction, BalanceOfOutputDTO>)callList[i - startItem])
                    .Output.Balance;
                if (balance > 0)
                {
                    var tokenBalance = new EvmBalance()
                    {
                        Balance = Web3.Convert.FromWei(balance, tokens[i].Decimals),
                        TokenName = tokens[i].Symbol,
                        TokenAddress = tokens[i].Address,
                        Chain = chain
                    };

                    var geckoToken = geckoTokens.FirstOrDefault(x =>
                        string.Equals(x.Symbol, tokens[i].Symbol, StringComparison.InvariantCultureIgnoreCase));

                    evmTokens.Add((tokenBalance, geckoToken)!);
                }
            }

            if (evmTokens.Count > 0)
            {
                var ids = evmTokens.Select(x => x.GeckoToken?.Id).Distinct().ToList();
                var prices = await GetPrices(ids).ConfigureAwait(false);

                foreach (var balance in evmTokens)
                {
                    if (balance.GeckoToken != null)
                    {
                        var price = prices[balance.GeckoToken.Id.ToLower()];
                        balance.Balance.Price = price["usd"];
                        balance.Balance.Value = balance.Balance.Price * balance.Balance.Balance;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // TODO : Handle error
            // No enable to reach rpc 

            var test = ex.Message;
        }

        return evmTokens.Select(e => e.Balance).ToList();
    }

    public async Task<Dictionary<string, Dictionary<string, decimal>>> GetPrices(List<string> geckoIds)
    {
        var idsCombined = string.Join(",", geckoIds);
        return await _httpClient.GetFromJsonAsync<Dictionary<string, Dictionary<string, decimal>>>(
            "https://api.coingecko.com/api/v3/simple/price?ids=" + idsCombined + "&vs_currencies=usd");
    }

    public async Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress)
    {
        try
        {
            // Define the assets and chains we want to query
            var assets = new[] { Ticker.USDC, Ticker.ETH };
            var chains = new[] { "arbitrum", "ethereum" };

            // Get balances from Web3Proxy service
            var balances = await _web3ProxyService.GetWalletBalanceAsync(publicAddress, assets, chains);

            // Convert Balance objects to EvmBalance objects
            var evmBalances = new List<EvmBalance>();
            
            foreach (var balance in balances)
            {
                if (balance.Amount > 0)
                {
                    var evmBalance = new EvmBalance
                    {
                        Balance = balance.Amount,
                        TokenName = balance.TokenName ?? "Unknown",
                        TokenAddress = balance.TokenAdress ?? "",
                        TokenImage = balance.TokenImage ?? "",
                        Price = balance.Price,
                        Value = balance.Value,
                        Chain = new Chain
                        {
                            Id = balance.Chain?.Id ?? "",
                            Name = balance.Chain?.Name ?? "Unknown",
                            ChainId = balance.Chain?.ChainId ?? 0,
                            RpcUrl = balance.Chain?.RpcUrl ?? ""
                        }
                    };
                    evmBalances.Add(evmBalance);
                }
            }


            var othersChains = ChainService.GetChains();

            foreach (var chain in othersChains)
            {
                evmBalances.AddRange(await GetAllBalances(chain, publicAddress));
            }

            return evmBalances;
        }
        catch (Exception ex)
        {
            // Log the exception and fallback to original implementation if Web3Proxy fails
            Console.WriteLine($"Web3Proxy balance retrieval failed: {ex.Message}");
            
            var chainBalances = new List<EvmBalance>();
            var chains = ChainService.GetChains();

            foreach (var chain in chains)
            {
                chainBalances.AddRange(await GetAllBalances(chain, publicAddress));
            }

            return chainBalances;
        }
    }

    public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe,
        bool isFirstCall = false)
    {
        string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe);
        int limit = isFirstCall ? 10000 : CalculateCandleLimit(startDate, timeframe);

        GmxV2Prices? gmxPrices = null;
        int maxRetries = 3;
        int delayMs = 1000;

        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                gmxPrices = await _httpClient.GetFromJsonAsync<GmxV2Prices>(
                    $"https://arbitrum-api.gmxinfra.io/prices/candles?tokenSymbol={ticker}&period={gmxTimeframe}&limit={limit}");
                break;
            }
            catch (HttpRequestException ex) when (ex.InnerException is IOException)
            {
                Console.Error.WriteLine($"Attempt {attempt}: Network error while fetching candles: {ex.Message}");
                if (attempt == maxRetries)
                    throw;
                await Task.Delay(delayMs * attempt);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Unexpected error: {ex.Message}");
                throw;
            }
        }

        if (gmxPrices == null)
            return null;

        var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.AddMinutes(-1).ToUnixTimestamp()).ToList();
        var candles = new List<Candle>();
        var timeBetweenCandles =
            gmxPrices.Candles.Count > 2 ? gmxPrices.Candles[0][0] - gmxPrices.Candles[1][0] : 900; // Default 15 minutes

        for (int i = 0; i < filteredCandles.Count; i++)
        {
            var c = GmxV2Mappers.Map(filteredCandles[i], ticker, timeframe, (int)timeBetweenCandles);
            candles.Add(c);
        }

        return candles.OrderBy(c => c.Date).ToList();
    }

    private int CalculateCandleLimit(DateTime startDate, Timeframe timeframe)
    {
        var now = DateTime.UtcNow;
        var minutesPerCandle = timeframe switch
        {
            Timeframe.OneMinute => 1,
            Timeframe.FiveMinutes => 5,
            Timeframe.FifteenMinutes => 15,
            Timeframe.ThirtyMinutes => 30,
            Timeframe.OneHour => 60,
            Timeframe.FourHour => 240,
            Timeframe.OneDay => 1440,
            _ => 15
        };
        var totalMinutes = (now - startDate).TotalMinutes;
        var candlesNeeded = (int)Math.Ceiling(totalMinutes / minutesPerCandle);
        return Math.Min(candlesNeeded + 5, 10000);
    }

    public decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker)
    {
        var subgraph = GetSubgraph(subgraphProvider);
        var volume = subgraph.GetVolume(ticker).Result;
        return volume;
    }

    private ISubgraphPrices GetSubgraph(SubgraphProvider subgraphProvider)
    {
        return _subgraphs.First(s => s.GetProvider() == subgraphProvider);
    }

    public async Task<List<Ticker>> GetAvailableTicker()
    {
        var tokenList = await _httpClient.GetFromJsonAsync<GmxV2TokenList>(
            "https://arbitrum-api.gmxinfra.io/tokens");

        if (tokenList == null)
            return null;

        return GmxV2Mappers.Map(tokenList);
    }

    public async Task<Candle> GetCandle(Ticker ticker)
    {
        var lastCandles = await GetCandles(ticker, DateTime.UtcNow.AddMinutes(-5),
            Timeframe.OneMinute);
        return lastCandles.Last();
    }

    public async Task<PrivyInitAddressResponse> InitAddress(string publicAddress)
    {
        try
        {
            var response = await _web3ProxyService.CallPrivyServiceAsync<PrivyInitAddressResponse>(
                "/init-address",
                new { address = publicAddress });

            return response;
        }
        catch (Exception ex)
        {
            // Log the error
            Console.Error.WriteLine($"Error initializing address: {ex.Message}");
            return new PrivyInitAddressResponse
            {
                Success = false,
                Error = ex.Message
            };
        }
    }

    public async Task<BigInteger> GetAllowance(string publicAddress, Ticker ticker)
    {
        var contractAddress = TokenService.GetContractAddress(ticker);
        var allowance = await EvmBase.GetAllowance(_web3, publicAddress, contractAddress);
        return allowance;
    }

    public async Task<bool> Send(
        Chain chain,
        Ticker ticker,
        decimal amount,
        string publicAddress,
        string privateKey,
        string receiverAddress)
    {
        var account = new Wallet(privateKey, _password).GetAccount(publicAddress);
        var web3 = new Web3(account, chain.RpcUrl);

        try
        {
            if (ticker == Ticker.ETH)
            {
                return await SendEth(amount, receiverAddress, web3);
            }

            return await SendToken(ticker, amount, publicAddress, receiverAddress, web3);
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    private static async Task<bool> SendEth(decimal amount, string receiverAddress, Web3 web3)
    {
        web3.TransactionManager.UseLegacyAsDefault = true;
        var ethService = web3.Eth.GetEtherTransferService();
        var gas = await ethService.EstimateGasAsync(receiverAddress, amount);
        var transaction = await ethService.TransferEtherAndWaitForReceiptAsync(receiverAddress, amount, gas: gas);

        return transaction.Status.Value == 1;
    }

    private static async Task<bool> SendToken(
        Ticker ticker,
        decimal amount,
        string senderAddress,
        string receiverAddress,
        Web3 web3)
    {
        var contractAddress = TokenService.GetContractAddress(ticker);
        var transactionMessage = new TransferFunction
        {
            FromAddress = senderAddress,
            To = receiverAddress,
            Value = Web3.Convert.ToWei(amount)
        };

        var transferHandler = web3.Eth.GetContractTransactionHandler<TransferFunction>();
        var transferReceipt =
            await transferHandler.SendRequestAndWaitForReceiptAsync(contractAddress, transactionMessage);

        var transaction =
            await web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(transferReceipt.TransactionHash);
        return transaction != null;
    }

    public async Task<bool> CancelOrders(Account account, Ticker ticker)
    {
        if (account.IsPrivyWallet)
        {
            try
            {
                var response = await _web3ProxyService.CallGmxServiceAsync<Web3ProxyResponse>("/cancel-orders",
                    new
                    {
                        account = account.Key,
                        walletId = account.Secret,
                        ticker = ticker.ToString()
                    });

                return response.Success;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error canceling orders via Fastify API: {ex.Message}");
                return false;
            }
        }
        else
        {
            var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
            var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
            var web3 = new Web3(wallet, chain.RpcUrl);
            var service = new GmxV2Service();
            return await service.CancelOrders(web3, account.Key, ticker);
        }
    }

    public async Task<Trade> IncreasePosition(
        Account account,
        Ticker ticker,
        TradeDirection direction,
        decimal price,
        decimal quantity,
        decimal? leverage,
        decimal? stopLossPrice = null,
        decimal? takeProfitPrice = null)
    {
        Trade trade = null;

        try
        {
            // If this is a Privy wallet, call the GMX service through Fastify API
            if (account.IsPrivyWallet)
            {
                try
                {
                    var response = await _web3ProxyService.CallGmxServiceAsync<object>("/open-position",
                        new
                        {
                            account = account.Key,
                            walletId = account.Secret,
                            tradeType = price > 0 ? "limit" : "market",
                            ticker = ticker.ToString(),
                            direction = direction.ToString(),
                            price = price,
                            quantity,
                            leverage = leverage ?? 1.0m,
                            stopLossPrice = stopLossPrice,
                            takeProfitPrice = takeProfitPrice
                        });

                    // Create a trade object using the returned hash
                    var tradeType = price > 0 ? TradeType.Limit : TradeType.Market;
                    var tradeStatus = TradeStatus.Requested; // Use a valid enum value that exists in TradeStatus

                    trade = new Trade(
                        DateTime.UtcNow,
                        direction,
                        tradeStatus,
                        tradeType,
                        ticker,
                        quantity,
                        price,
                        leverage ?? 1.0m,
                        account.Key,
                        ""
                    );
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
            else
            {
                // Continue with the existing direct service call for non-Privy wallets
                var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
                var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
                var web3 = new Web3(wallet, chain.RpcUrl);

                trade = await _gmxV2Service.IncreasePosition(web3, account.Key, ticker, direction, price, quantity,
                    leverage);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return trade;
    }

    public async Task<Trade> DecreasePosition(
        Account account,
        Ticker ticker,
        TradeDirection direction,
        decimal price,
        decimal quantity,
        decimal? leverage)
    {
        Trade trade = null;

        if (account.IsPrivyWallet)
        {
            try
            {
                var response = await _web3ProxyService.CallGmxServiceAsync<ClosePositionResponse>("/close-position",
                    new
                    {
                        account = account.Key,
                        ticker = ticker.ToString(),
                        direction = direction == TradeDirection.Long
                            ? TradeDirection.Short.ToString()
                            : TradeDirection.Long.ToString(),
                    });
                trade = new Trade(
                    DateTime.UtcNow,
                    direction,
                    TradeStatus.Requested,
                    TradeType.Market,
                    ticker,
                    quantity,
                    price,
                    leverage ?? 1,
                    response.Hash,
                    ""
                );

                return trade;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
        var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
        var web3 = new Web3(wallet, chain.RpcUrl);

        try
        {
            trade = await _gmxV2Service.DecreasePosition(web3, account.Key, ticker, direction, price, quantity,
                leverage ?? 1);
        }
        catch (Exception ex)
        {
            throw;
        }

        return trade;
    }

    public async Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker,
        TradeDirection direction, decimal price, decimal quantity, decimal? leverage,
        decimal? stopLossPrice = null,
        decimal? takeProfitPrice = null)
    {
        var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
        var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
        var web3 = new Web3(wallet, chain.RpcUrl);

        Trade trade;
        try
        {
            // TODO: This method in GmxV2Service might not exist or needs different handling for Privy wallets.
            // Commenting out for now as IncreasePosition is the priority.
            // trade = await _gmxV2Service.DecreaseOrder(web3, account.Key, tradeType, ticker, direction, price,
            //     quantity, leverage);
            trade = null; // Placeholder return
        }
        catch (Exception ex)
        {
            throw;
        }

        return trade;
    }

    public async Task<Trade> GetTrade(Account account, string chainName, Ticker ticker)
    {
        if (account.IsPrivyWallet)
        {
            var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxPositionsResponse>(
                "/positions",
                new { account = account.Key, ticker = ticker.ToString() });

            var position = result.Positions.FirstOrDefault(p => p.Ticker == ticker.ToString());

            if (position == null)
                return null;

            // TODO: Map the position object to a Trade object
            var trade = new Trade(
                position.Date,
                MiscExtensions.ParseEnum<TradeDirection>(position.Direction),
                MiscExtensions.ParseEnum<TradeStatus>(position.Status),
                MiscExtensions.ParseEnum<TradeType>(position.TradeType),
                MiscExtensions.ParseEnum<Ticker>(position.Ticker),
                (decimal)position.Quantity,
                (decimal)position.Price,
                (decimal?)position.Leverage,
                account.Key,
                position.ExchangeOrderId
            );

            return trade;
        }

        return await GetTrade(account.Key, chainName, ticker);
    }

    public async Task<List<Position>> GetPositions(Account account)
    {
        if (account.IsPrivyWallet)
        {
            var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxPositionsResponse>(
                "/positions",
                new { account = account.Key });


            return GmxV2Mappers.Map(result.Positions);
        }

        throw new NotImplementedException();
    }


    public async Task<Trade> GetTrade(string reference, string chainName, Ticker ticker)
    {
        var chain = ChainService.GetChain(chainName);
        var web3 = new Web3(chain.RpcUrl);
        return await _gmxV2Service.GetTrade(web3, reference, ticker);
    }

    public async Task<List<FundingRate>> GetFundingRates()
    {
        return await _web3ProxyService.CallGmxServiceAsync<List<FundingRate>>("/gmx/funding-rates", new { });
    }

    public async Task<decimal> QuantityInPosition(string chainName, Account account, Ticker ticker)
    {
        if (account.IsPrivyWallet)
        {
            var positions = await GetPositions(account);
            var positionForTicker = positions.FirstOrDefault(p => p.Ticker == ticker);
            if (positionForTicker != null)
            {
                return positionForTicker.Open.Quantity;
            }
            else
            {
                return 0;
            }
        }

        var chain = ChainService.GetChain(chainName);
        var web3 = new Web3(chain.RpcUrl);
        var quantity = await _gmxV2Service.QuantityInPosition(web3, account.Key, ticker);
        return quantity;
    }

    public async Task<decimal> GetFee(string chainName)
    {
        var chain = ChainService.GetChain(chainName);
        var web3 = new Web3(chain.RpcUrl);
        var etherPrice = (await GetPrices(new List<string> { "ethereum" }))["ethereum"]["usd"];
        var fee = await GmxService.GetFee(web3, etherPrice);
        return fee;
    }

    public async Task<List<Trade>> GetOrders(Account account, Ticker ticker)
    {
        if (account.IsPrivyWallet)
        {
            var result = await _web3ProxyService.GetGmxServiceAsync<GetGmxTradesResponse>("/trades",
                new { account = account.Key, ticker = ticker.ToString() });

            return GmxV2Mappers.Map(result.Trades);
        }
        else
        {
            var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
            var web3 = new Web3(chain.RpcUrl);
            // var orders = await GmxService.GetOrders(web3, account.Key, ticker);
            var orders = await _gmxV2Service.GetOrders(web3, account.Key, ticker);

            return GmxV2Mappers.Map(orders);
        }

        return new List<Trade>();
    }

    public async Task<bool> SetAllowance(Account account, Ticker ticker, BigInteger amount)
    {
        if (account.IsPrivyWallet)
        {
            var allowance = await _web3ProxyService.CallPrivyServiceAsync<PrivyApproveTokenResponse>("/approve-token",
                new
                {
                    address = account.Key, walletId = account.Secret, ticker = ticker.ToString(),
                    amount = amount.Equals(0) ? null : amount.ToString()
                });
            return false;
        }

        var web3 = BuildWeb3ForAccount(account);
        var contractAddress = TokenService.GetContractAddress(ticker);
        var approval = await EvmBase.ApproveToken(web3, account.Key, contractAddress,
            Arbitrum.AddressV2.SyntheticsRouter, amount);
        return approval;
    }

    private Web3 BuildWeb3ForAccount(Account account)
    {
        var wallet = new Wallet(account.Secret, _password).GetAccount(account.Key);
        var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
        return new Web3(wallet, chain.RpcUrl);
    }


    public async Task<(string Id, string Address)> CreatePrivyWallet()
    {
        var privyWallet = await _web3ProxyService.CallPrivyServiceAsync<PrivyWallet>("/privy/create-wallet", new { });
        return (privyWallet.Id, privyWallet.Address);
    }

    /// <summary>
    /// Signs a message using the embedded wallet
    /// </summary>
    /// <param name="embeddedWalletId">The wallet id of the embedded wallet</param>
    /// <param name="address">The address of the embedded wallet</param>
    /// <param name="message">The message to sign</param>
    /// <returns>The signature response</returns>
    public async Task<string> SignMessageAsync(string embeddedWalletId, string address, string message)
    {
        // Construct the request body using the exact format from Privy documentati
        var requestBody = new
        {
            address = address,
            walletId = embeddedWalletId,
            message = message,
        };

        var response =
            await _web3ProxyService.CallPrivyServiceAsync<PrivySigningResponse>("sign-message", requestBody);
        return response.Signature;
    }

    // Overload to match IEvmManager interface
    public async Task<List<Candle>> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe)
    {
        return await GetCandles(ticker, startDate, timeframe, false);
    }
}