using System.Numerics;
using Managing.ABI.GmxV2.Reader.ContractDefinition;
using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Gmx.v1;
using Managing.Infrastructure.Evm.Models.Gmx.v2;
using Managing.Infrastructure.Evm.Referentials;
using Nethereum.Util;
using Nethereum.Web3;
using static Managing.Common.Enums;

namespace Managing.Infrastructure.Evm.Services.Gmx;

public static class GmxMappers
{
    internal static Trade Map(GmxPosition? position, Ticker ticker)
    {
        if (position == null)
        {
            return null;
        }

        var leverage = position.SizeDelta / position.Collateral;

        var trade = new Trade(
            DateHelpers.GetFromUnixTimestamp((int)position.LastIncreasedTime),
            position.IsLong ? TradeDirection.Long : TradeDirection.Short,
            TradeStatus.Filled,
            TradeType.Limit,
            ticker,
            Web3.Convert.FromWei(position.SizeDelta, 30),
            Web3.Convert.FromWei(position.AveragePrice, 30),
            (decimal)leverage,
            "",
            "");

        return trade;
    }

    public static List<GmxPosition> MapPositions(List<BigInteger> positionData,
        (List<string> CollateralTokens, List<string> IndexTokens, List<bool> IsLong) queryData)
    {
        var gmxPositions = new List<GmxPosition>();
        var propLength = 9;

        for (int i = 0; i < queryData.CollateralTokens.Count; i++)
        {
            var gmxPosition = new GmxPosition
            {
                CollateralToken = queryData.CollateralTokens[i],
                IndexToken = queryData.IndexTokens[i],
                IsLong = queryData.IsLong[i],
                SizeDelta = positionData[i * propLength],
                Collateral = positionData[i * propLength + 1],
                AveragePrice = positionData[i * propLength + 2],
                EntryFundingRate = positionData[i * propLength + 3],
                //gmxPosition.CumulativeFundingRate = collateralToken.cumulativeFundingRate;
                HasRealisedProfit = positionData[i * propLength + 4].Equals(1),
                RealisedPnl = positionData[i * propLength + 5],
                LastIncreasedTime = positionData[i * propLength + 6],
                HasProfit = positionData[i * propLength + 7],
                Delta = positionData[i * propLength + 8]
            };

            if (!gmxPosition.SizeDelta.IsZero)
                gmxPositions.Add(gmxPosition);
        }

        return gmxPositions;
    }

    public static List<GmxOrder> MapIncrease(
        List<BigInteger> orderData,
        List<string> addressData,
        List<BigInteger> indexes)
    {
        var extractor = (List<BigInteger> orderProperty, List<string> address) =>
        {
            var order = new GmxOrder
            {
                PurchaseToken = address[0].ToString(),
                CollateralToken = address[1].ToString(),
                IndexToken = address[2].ToString(),
                PurchaseTokenAmount = Web3.Convert.FromWeiToBigDecimal(orderProperty[0], 18).ToString(),
                SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(),
                IsLong = orderProperty[2].ToString() == "1",
                TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(),
                TriggerAboveThreshold = orderProperty[4].ToString() == "1",
                Type = GmxOrderType.Increase
            };

            return order;
        };

        return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 3);
    }

    public static List<GmxOrder> MapDecrease(
        List<BigInteger> orderData,
        List<string> addressData,
        List<BigInteger> indexes)
    {
        var extractor = (List<BigInteger> orderProperty, List<string> address) =>
        {
            var order = new GmxOrder
            {
                CollateralToken = address[0],
                IndexToken = address[1],
                CollateralDelta = orderProperty[0].ToString(),
                SizeDelta = Web3.Convert.FromWeiToBigDecimal(orderProperty[1], 30).ToString(),
                IsLong = orderProperty[2].ToString() == "1",
                TriggerPrice = Web3.Convert.FromWeiToBigDecimal(orderProperty[3], 30).ToString(),
                TriggerAboveThreshold = orderProperty[4].ToString() == "1",
                Type = GmxOrderType.Decrease
            };

            return order;
        };

        return ParseOrdersData(orderData, addressData, extractor, indexes, 5, 2);
    }

    public static List<GmxOrder> ParseOrdersData(
        List<BigInteger> orderData,
        List<string> addressData,
        Func<List<BigInteger>, List<string>, GmxOrder> extractor,
        List<BigInteger> indexes,
        int uintPropsLength,
        int addressPropsLength)
    {
        if (orderData.Count == 0 || addressData.Count == 0)
        {
            return new List<GmxOrder>();
        }

        var count = orderData.Count / uintPropsLength;

        var orders = new List<GmxOrder>();
        for (int i = 0; i < count; i++)
        {
            var slicedAddress = addressData
                .Skip(addressPropsLength * i)
                .Take(addressPropsLength)
                .ToList();

            if (slicedAddress[0] == Arbitrum.Address.Zero && slicedAddress[1] == Arbitrum.Address.Zero)
            {
                continue;
            }

            var slicedProperty = orderData
                .Skip(uintPropsLength * i)
                .Take(uintPropsLength * i)
                .ToList();

            var order = extractor(slicedProperty, slicedAddress);
            order.Index = indexes[i];
            orders.Add(order);
        }

        return orders;
    }

    internal static Candle Map(GmxOhlc price, Ticker ticker, Timeframe timeframe)
    {
        return new Candle()
        {
            Date = DateHelpers.GetFromUnixTimestamp(price.t),
            OpenTime = DateHelpers.GetFromUnixTimestamp(price.t).AddSeconds(-1),
            Open = Convert.ToDecimal(price.o),
            High = Convert.ToDecimal(price.h),
            Low = Convert.ToDecimal(price.l),
            Close = Convert.ToDecimal(price.c),
            Exchange = TradingExchanges.Evm,
            Ticker = ticker.ToString(),
            Timeframe = timeframe
        };
    }

    internal static GmxMarket Map(MarketInfo infos)
    {
        var indexToken =
            TokenV2Service.GetTokenByAddress(TokenV2Service.ConvertTokenAddress(infos.Market.IndexToken, "native"));
        var longToken = TokenV2Service.GetTokenByAddress(infos.Market.LongToken);
        var shortToken = TokenV2Service.GetTokenByAddress(infos.Market.ShortToken);
        var isSameCollaterals = infos.Market.LongToken == infos.Market.ShortToken;
        var isSpotOnly = infos.Market.IndexToken == AddressUtil.ZERO_ADDRESS;
        var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
            isSpotOnly);
        var symbol = indexToken.Symbol;

        return new GmxMarket
        {
            MarketToken = infos.Market.MarketToken,
            IndexToken = infos.Market.IndexToken,
            LongToken = infos.Market.LongToken,
            ShortToken = infos.Market.ShortToken,
            Name = name,
            Symbol = symbol,
            IsSpotOnly = isSpotOnly,
            IsSameCollaterals = isSameCollaterals,
            FundingFactorPerSecond = infos.NextFunding.FundingFactorPerSecond,
            LongsPayShorts = infos.NextFunding.LongsPayShorts
        };
    }

    public static GmxMarket MapInfos(MarketsProps infos)
    {
        var indexToken =
            TokenV2Service.GetTokenByAddress(TokenV2Service.ConvertTokenAddress(infos.IndexToken, "native"));
        var longToken = TokenV2Service.GetTokenByAddress(infos.LongToken);
        var shortToken = TokenV2Service.GetTokenByAddress(infos.ShortToken);
        var isSameCollaterals = infos.LongToken == infos.ShortToken;
        var isSpotOnly = infos.IndexToken == AddressUtil.ZERO_ADDRESS;
        var name = TokenV2Service.GetMarketFullName(indexToken.Address, longToken.Address, shortToken.Address,
            isSpotOnly);

        return new GmxMarket
        {
            MarketToken = infos.MarketToken,
            IndexToken = infos.IndexToken,
            LongToken = infos.LongToken,
            ShortToken = infos.ShortToken,
            Name = name,
            Symbol = indexToken.Symbol,
            IsSpotOnly = isSpotOnly,
            IsSameCollaterals = isSameCollaterals,
        };
    }

    public static GmxMarketTokenPrice Map(GetMarketTokenPriceOutputDTO response)
    {
        return new GmxMarketTokenPrice
        {
            LongTokenAmount = response.ReturnValue2.LongTokenAmount,
            ShortTokenAmount = response.ReturnValue2.ShortTokenAmount,
            LongTokenUsd = response.ReturnValue2.LongTokenUsd,
            ShortTokenUsd = response.ReturnValue2.ShortTokenUsd,
            PoolValue = response.ReturnValue2.PoolValue,
            LongPnl = response.ReturnValue2.LongPnl,
            ShortPnl = response.ReturnValue2.ShortPnl,
            NetPnl = response.ReturnValue2.NetPnl,
            TotalBorrowingFees = response.ReturnValue2.TotalBorrowingFees,
            BorrowingFeePoolFactor = response.ReturnValue2.BorrowingFeePoolFactor,
            ImpactPoolAmount = response.ReturnValue2.ImpactPoolAmount
        };
    }

    internal static List<Trade> Map(List<GmxOrder> orders, Ticker ticker)
    {
        return orders.ConvertAll(order => Map(order, ticker));
    }

    private static Trade Map(GmxOrder order, Ticker ticker)
    {
        var trade = new Trade(DateTime.UtcNow,
            order.IsLong ? TradeDirection.Short : TradeDirection.Long,
            TradeStatus.Requested,
            GmxHelpers.GetTradeType(order.IsLong, order.TriggerAboveThreshold),
            ticker,
            Convert.ToDecimal(order.SizeDelta),
            Convert.ToDecimal(order.TriggerPrice),
            null,
            "", ""
        );

        return trade;
    }

    public static List<FundingRate> Map(GmxMarketInfo market)
    {
        var longApr =
            GmxV2Helpers.GetFundingFactorPerPeriod(market, true, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
        var shortApr =
            GmxV2Helpers.GetFundingFactorPerPeriod(market, false, GmxV2Helpers.Periods[Timeframe.OneHour]) * 100;
        var longFundingRate = new FundingRate()
        {
            Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
            Rate = GmxHelpers.FormatAmount(longApr, 30, 4),
            Direction = TradeDirection.Long
        };
        var shortFundingRate = new FundingRate()
        {
            Ticker = GmxHelpers.GetTicker(market.Market.Symbol),
            Rate = GmxHelpers.FormatAmount(shortApr, 30, 4),
            Direction = TradeDirection.Short
        };
        return new List<FundingRate>() { longFundingRate, shortFundingRate };
    }
}