using System.Globalization;
using System.Numerics;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Signer;
using Nethereum.Web3;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Evm.Services.Gmx;

public static class GmxHelpers
{
    public static decimal GetQuantityForLeverage(decimal quantity, decimal? leverage)
    {
        return leverage.HasValue ? leverage.Value * quantity : quantity;
    }

    public static (List<string> CollateralTokens, List<string> IndexTokens, List<bool> IsLong) GetPositionQueryData(
        List<string> contractAddress)
    {
        var collateralToken = new List<string>();
        var indexTokens = new List<string>();
        var isLongs = new List<bool>();

        foreach (var token in contractAddress)
        {
            collateralToken.Add(token);
            indexTokens.Add(token);
            isLongs.Add(true);
        }

        foreach (var token in contractAddress)
        {
            collateralToken.Add(Arbitrum.Address.USDC);
            indexTokens.Add(token);
            isLongs.Add(false);
        }

        return (collateralToken, indexTokens, isLongs);
    }

    public static List<BigInteger> GetIndexesRange(int lastIndex)
    {
        var indexes = new List<BigInteger>();

        var limit = 15;
        var from = (lastIndex - limit) < 0 ? 0 : lastIndex - limit;

        for (int i = from; i <= lastIndex; i++)
        {
            indexes.Add(new BigInteger(i));
        }

        return indexes;
    }

    public static BigInteger GetAcceptablePrice(decimal price, bool isLong)
    {
        decimal priceBasisPoints;
        var basisPointDivisor = 10000m;
        var allowedSlippage = 30m;
        var toDecimal = 30;

        if (isLong)
        {
            priceBasisPoints = basisPointDivisor - allowedSlippage;
        }
        else
        {
            priceBasisPoints = basisPointDivisor + allowedSlippage;
        }

        var test = Web3.Convert.ToWei(price, toDecimal) * new BigInteger(priceBasisPoints);

        var priceLimit = test / Web3.Convert.ToWei(basisPointDivisor, 0);

        return priceLimit;
    }

    public static bool GetTriggerAboveThreshold(bool isLong, TradeType tradeType)
    {
        if ((isLong && tradeType == TradeType.TakeProfit) ||
            (!isLong && tradeType == TradeType.StopLoss))
        {
            return true;
        }

        return false;
    }

    public static TradeType GetTradeType(bool isLong, bool isAboveThreshold)
    {
        if ((isLong && isAboveThreshold) ||
            (!isLong && !isAboveThreshold))
        {
            return TradeType.TakeProfit;
        }

        return TradeType.StopLoss;
    }

    internal static string GeTimeframe(Timeframe timeframe)
    {
        return timeframe switch
        {
            Timeframe.OneMinute => "1m",
            Timeframe.FiveMinutes => "5m",
            Timeframe.FifteenMinutes => "15m",
            Timeframe.ThirtyMinutes => "30m",
            Timeframe.OneHour => "1h",
            Timeframe.FourHour => "4h",
            Timeframe.OneDay => "1d",
            _ => throw new NotImplementedException(),
        };
    }

    public static string GetRandomAddress()
    {
        var ecKey = EthECKey.GenerateKey();
        var privateKey = ecKey.GetPrivateKeyAsBytes().ToHex();
        var account = new EthECKey(privateKey).GetPublicAddress();
        return account;
    }

    internal static MarketPrices GetContractMarketPrices(List<GmxTokenData> tokens, GmxMarket market)
    {
        var indexToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.IndexToken));
        var longToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.LongToken));
        var shortToken = tokens.First(t => GmxV2Helpers.SameAddress(t.Address, market.ShortToken));

        return new MarketPrices()
        {
            IndexTokenPrice = ConvertToContractTokenPrice(indexToken.Price, indexToken.Decimals),
            LongTokenPrice = ConvertToContractTokenPrice(longToken.Price, longToken.Decimals),
            ShortTokenPrice = ConvertToContractTokenPrice(shortToken.Price, shortToken.Decimals)
        };
    }

    private static MarketPrice ConvertToContractTokenPrice(MarketPrice marketPrice, int tokenDecimals)
    {
        var price = new MarketPrice();
        price.Min = ConvertToContractPrice(marketPrice.Min, tokenDecimals);
        price.Max = ConvertToContractPrice(marketPrice.Max, tokenDecimals);
        return price;
    }

    private static BigInteger ConvertToContractPrice(BigInteger price, decimal tokenDecimals)
    {
        // Convert the decimal price to a BigInteger, scaling it up to maintain precision.
        BigInteger scaledPrice = price * new BigInteger((decimal)Math.Pow(10, (double)tokenDecimals));

        // Return the scaled price directly since it's already adjusted for token decimals.
        return scaledPrice;
    }

    public static Ticker GetTicker(string marketName)
    {
        try
        {
            var indexToken = marketName.Split(' ')[0];
            var ticker = MiscExtensions.ParseEnum<Ticker>(indexToken);
            return ticker;
        }
        catch (Exception e)
        {
            return Ticker.Unknown;
        }
    }

    public static decimal FormatAmount(BigInteger? amount, int tokenDecimals, int? displayDecimals = null,
        bool useCommas = false, string defaultValue = "...")
    {
        if (amount == null || amount == BigInteger.Zero)
        {
            return 0;
        }

        if (displayDecimals == null)
        {
            displayDecimals = 4;
        }

        var amountStr = Web3.Convert.FromWei(amount.Value, tokenDecimals).ToString(CultureInfo.InvariantCulture);
        amountStr = LimitDecimals(amountStr, displayDecimals.Value);

        if (displayDecimals != 0)
        {
            amountStr = PadDecimals(amountStr, displayDecimals.Value);
        }

        if (useCommas)
        {
            amountStr = NumberWithCommas(amountStr);
        }

        return decimal.Parse(amountStr, CultureInfo.InvariantCulture);
    }

    private static string LimitDecimals(string amountStr, int displayDecimals)
    {
        var parts = amountStr.Split('.');
        if (parts.Length == 2 && parts[1].Length > displayDecimals)
        {
            parts[1] = parts[1].Substring(0, displayDecimals);
        }

        return string.Join(".", parts);
    }

    private static string PadDecimals(string amountStr, int displayDecimals)
    {
        var parts = amountStr.Split('.');
        if (parts.Length == 1)
        {
            return amountStr + "." + new string('0', displayDecimals);
        }
        else if (parts.Length == 2)
        {
            return parts[0] + "." + parts[1].PadRight(displayDecimals, '0');
        }

        return amountStr;
    }

    private static string NumberWithCommas(string amountStr)
    {
        var parts = amountStr.Split('.');
        parts[0] = int.Parse(parts[0]).ToString("N0", CultureInfo.InvariantCulture);
        return string.Join(".", parts);
    }

    public static BigInteger? ConvertToUsd(BigInteger? tokenAmount, int? tokenDecimals, BigInteger? price)
    {
        if (tokenAmount == null || tokenDecimals == null || price == null)
        {
            return null;
        }

        return (tokenAmount.Value * price.Value) / ExpandDecimals(1, tokenDecimals.Value);
    }

    private static BigInteger ExpandDecimals(int value, int decimals)
    {
        return BigInteger.Multiply(value, BigInteger.Pow(10, decimals));
    }
}