using System.Numerics;
using Managing.Common;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Nethereum.Web3;

namespace Managing.Infrastructure.Evm.Services.Gmx;

public static class GmxV2Helpers
{
    public static BigInteger GetFundingFactorPerPeriod(GmxMarketInfo marketInfo, bool isLong, int periodInSeconds)
    {
        var fundingFactorPerSecond = marketInfo.Market.FundingFactorPerSecond;
        var longsPayShorts = marketInfo.Market.LongsPayShorts;
        var longInterestUsd = marketInfo.Infos.LongInterestUsd;
        var shortInterestUsd = marketInfo.Infos.ShortInterestUsd;

        var isLargerSide = isLong ? longsPayShorts : !longsPayShorts;

        BigInteger factorPerSecond;

        if (isLargerSide)
        {
            factorPerSecond = fundingFactorPerSecond * -1;
        }
        else
        {
            var largerInterestUsd = longsPayShorts ? longInterestUsd : shortInterestUsd;
            var smallerInterestUsd = longsPayShorts ? shortInterestUsd : longInterestUsd;

            var ratio = smallerInterestUsd > 0
                ? BigInteger.Multiply(largerInterestUsd, Precision) / smallerInterestUsd
                : 0;

            factorPerSecond = ApplyFactor(ratio, fundingFactorPerSecond);
        }

        return factorPerSecond * periodInSeconds;
    }

    private static BigInteger ApplyFactor(BigInteger ratio, BigInteger fundingFactorPerSecond)
    {
        // Implement the logic for applying the factor based on the ratio
        // This is a placeholder implementation
        return BigInteger.Multiply(ratio, fundingFactorPerSecond) / Precision;
    }

    private static readonly BigInteger Precision = BigInteger.Pow(10, 18);

    public static Dictionary<Enums.Timeframe, int> Periods = new Dictionary<Enums.Timeframe, int>()
    {
        { Enums.Timeframe.FiveMinutes, 300 },
        { Enums.Timeframe.FifteenMinutes, 900 },
        { Enums.Timeframe.OneHour, 3600 },
        { Enums.Timeframe.FourHour, 14400 },
        { Enums.Timeframe.OneDay, 86400 }
    };

    public static string GetMarketAddress(Enums.Ticker ticker)
    {
        // Switch statement to return the market address based on the ticker
        switch (ticker)
        {
            case Enums.Ticker.BTC: return Constants.GMX.Markets.BTCUSDC;
            case Enums.Ticker.ETH: return Constants.GMX.Markets.ETHUSDC;
            case Enums.Ticker.LINK: return Constants.GMX.Markets.LINKUSD;
            case Enums.Ticker.PEPE: return Constants.GMX.Markets.PEPEUSD;
            case Enums.Ticker.SOL: return Constants.GMX.Markets.SOLUSD;
            case Enums.Ticker.UNI: return Constants.GMX.Markets.UNIUSD;
            case Enums.Ticker.SHIB: return Constants.GMX.Markets.SHIBUSD;
            case Enums.Ticker.AAVE: return Constants.GMX.Markets.AAVEUSD;
            case Enums.Ticker.OP: return Constants.GMX.Markets.OPUSD;
            case Enums.Ticker.APE: return Constants.GMX.Markets.APEUSD;
            case Enums.Ticker.GMX: return Constants.GMX.Markets.GMXUSD;
            case Enums.Ticker.ARB: return Constants.GMX.Markets.ARBUSD;
            case Enums.Ticker.NEAR: return Constants.GMX.Markets.NEARUSD;
            case Enums.Ticker.STX: return Constants.GMX.Markets.STXUSD;
            case Enums.Ticker.LTC: return Constants.GMX.Markets.LTCUSD;
            case Enums.Ticker.XRP: return Constants.GMX.Markets.XRPUSD;
            case Enums.Ticker.WIF: return Constants.GMX.Markets.WIFUSD;
            case Enums.Ticker.BNB: return Constants.GMX.Markets.BNBUSD;
            case Enums.Ticker.ORDI: return Constants.GMX.Markets.ORDIUSD;
            case Enums.Ticker.DOGE: return Constants.GMX.Markets.DOGEUSD;
            default: throw new ArgumentOutOfRangeException(nameof(ticker), "Invalid ticker value.");
        }
    }

    public static Enums.Ticker GetTicker(string marketAddress)
    {
        switch (marketAddress)
        {
            case Constants.GMX.Markets.BTCUSDC: return Enums.Ticker.BTC;
            case Constants.GMX.Markets.ETHUSDC: return Enums.Ticker.ETH;
            case Constants.GMX.Markets.LINKUSD: return Enums.Ticker.LINK;
            case Constants.GMX.Markets.PEPEUSD: return Enums.Ticker.PEPE;
            case Constants.GMX.Markets.SOLUSD: return Enums.Ticker.SOL;
            case Constants.GMX.Markets.UNIUSD: return Enums.Ticker.UNI;
            case Constants.GMX.Markets.SHIBUSD: return Enums.Ticker.SHIB;
            case Constants.GMX.Markets.AAVEUSD: return Enums.Ticker.AAVE;
            case Constants.GMX.Markets.OPUSD: return Enums.Ticker.OP;
            case Constants.GMX.Markets.APEUSD: return Enums.Ticker.APE;
            case Constants.GMX.Markets.GMXUSD: return Enums.Ticker.GMX;
            case Constants.GMX.Markets.ARBUSD: return Enums.Ticker.ARB;
            case Constants.GMX.Markets.NEARUSD: return Enums.Ticker.NEAR;
            case Constants.GMX.Markets.STXUSD: return Enums.Ticker.STX;
            case Constants.GMX.Markets.LTCUSD: return Enums.Ticker.LTC;
            case Constants.GMX.Markets.XRPUSD: return Enums.Ticker.XRP;
            case Constants.GMX.Markets.WIFUSD: return Enums.Ticker.WIF;
            case Constants.GMX.Markets.ORDIUSD: return Enums.Ticker.ORDI;
            default: throw new ArgumentOutOfRangeException(nameof(marketAddress), "Invalid market address.");
        }
    }

    public static Enums.TradeType GetTradeType(GmxV2OrderType orderType)
    {
        return orderType switch
        {
            GmxV2OrderType.LimitIncrease => Enums.TradeType.Limit,
            GmxV2OrderType.LimitDecrease => Enums.TradeType.Limit,
            GmxV2OrderType.MarketIncrease => Enums.TradeType.Market,
            GmxV2OrderType.MarketDecrease => Enums.TradeType.Market,
            GmxV2OrderType.Liquidation => Enums.TradeType.Market,
            _ => throw new ArgumentOutOfRangeException(nameof(orderType), "Invalid order type.")
        };
    }

    /// <summary>
    /// Parses the contract price to a decimal value.
    /// </summary>
    /// <param name="price"></param>
    /// <param name="tokenDecimals"></param>
    /// <param name="useTransform">Used to tranform the decimal since GMX is not using everytime the decimal from the MarketProps. Use true for prices and false for amounts</param>
    /// <returns></returns>
    public static decimal ParseContractPrice(BigInteger price, int tokenDecimals, bool useTransform = false)
    {
        if (useTransform)
            tokenDecimals = TransformValue(tokenDecimals);

        return (decimal)price / (decimal)ExpandDecimals(1, tokenDecimals);
    }

    public static BigInteger ConvertToContractPrice(decimal value, int tokenDecimals, bool useTransform = false)
    {
        if (useTransform)
            tokenDecimals = TransformValue(tokenDecimals);

        return new BigInteger(value * (decimal)ExpandDecimals(1, tokenDecimals));
    }

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

    public static int TransformValue(int input)
    {
        // Apply the derived linear equation y = -x + 30
        return -input + 30;
    }

    public static BigInteger EstimateOrderOraclePriceCount(int swapsCount)
    {
        return 3 + new BigInteger(swapsCount);
    }

    public static bool SameAddress(string address, string address2)
    {
        return string.Equals(address, address2, StringComparison.CurrentCultureIgnoreCase);
    }

    public static BigInteger GetAcceptablePrice(decimal price, bool isLong)
    {
        var slippage = 0.003m; // 0.3% slippage
        var priceSlippage = price * slippage;
        var acceptablePrice = isLong ? price + priceSlippage : price - priceSlippage;

        return Web3.Convert.ToWei(acceptablePrice, 22);
    }

    public static BigInteger GetCollateralAmount(decimal amount, decimal leverage = 1)
    {
        return Web3.Convert.ToWei(amount * leverage, 30);
    }

    public static decimal ConvertToUsd(BigInteger amount, int decimals, BigInteger price)
    {
        var result = (amount * price) / ExpandDecimals(1, decimals);
        return (decimal)result;
    }

    public static BigInteger ParseValue(decimal value, int decimals)
    {
        return Web3.Convert.ToWei(value, decimals);
    }

    public static string GetPositionKey(string addressesAccount, string addressesMarket,
        string addressesCollateralToken, bool flagsIsLong)
    {
        return $"{addressesAccount}:{addressesMarket}:{addressesCollateralToken}:{flagsIsLong}";
    }

    public static BigInteger GetEntryPrice(BigInteger positionSizeInUsd, BigInteger positionSizeInTokens,
        int decimals)
    {
        if (positionSizeInTokens <= 0)
        {
            return 0;
        }

        return BigInteger.Multiply(positionSizeInUsd, ExpandDecimals(1, decimals)) / positionSizeInTokens;
    }
}