using Exilion.TradingAtomics;
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using static Managing.Common.Enums;

namespace Managing.Domain.Shared.Helpers;

public static class TradingHelpers
{
    public static decimal GetHodlPercentage(Candle candle1, Candle candle2)
    {
        return candle2.Close * 100 / candle1.Close - 100;
    }

    public static decimal GetGrowthFromInitalBalance(decimal balance, decimal finalPnl)
    {
        var growth = balance + finalPnl;

        return growth * 100 / balance - 100;
    }

    public static PerformanceMetrics GetStatistics(Dictionary<DateTime, decimal> pnls)
    {
        var priceSeries = new TimePriceSeries(pnls.DistinctBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value));

        return priceSeries.CalculatePerformanceMetrics();
    }

    public static decimal GetFeeAmount(decimal fee, decimal amount)
    {
        return fee * amount;
    }

    public static decimal GetFeeAmount(decimal fee, decimal amount, TradingExchanges exchange)
    {
        if (exchange.Equals(TradingExchanges.Evm))
            return fee;

        return GetFeeAmount(fee, amount);
    }

    public static bool IsAGoodTrader(Trader trader)
    {
        return trader.Winrate > 30
               && trader.TradeCount > 8
               && trader.AverageWin > Math.Abs(trader.AverageLoss)
               && trader.Pnl > 0;
    }

    public static bool IsABadTrader(Trader trader)
    {
        return trader.Winrate < 30
               && trader.TradeCount > 8
               && trader.AverageWin * 3 < Math.Abs(trader.AverageLoss)
               && trader.Pnl < 0;
    }

    public static List<Trader> FindBadTrader(this List<Trader> traders)
    {
        var filteredTrader = new List<Trader>();
        foreach (var trader in traders)
        {
            if (IsABadTrader(trader))
            {
                filteredTrader.Add(trader);
            }
        }

        return filteredTrader;
    }

    public static List<Trader> FindGoodTrader(this List<Trader> traders)
    {
        var filteredTrader = new List<Trader>();
        foreach (var trader in traders)
        {
            if (IsAGoodTrader(trader))
            {
                filteredTrader.Add(trader);
            }
        }

        return filteredTrader;
    }

    public static List<Trader> MapToTraders(this List<Account> accounts)
    {
        var traders = new List<Trader>();
        foreach (var account in accounts)
        {
            traders.Add(new Trader
            {
                Address = account.Key
            });
        }

        return traders;
    }

    /// <summary>
    /// Calculates the total fees for a position based on GMX V2 fee structure
    /// </summary>
    /// <param name="position">The position to calculate fees for</param>
    /// <returns>The total fees for the position</returns>
    public static decimal CalculatePositionFees(Position position)
    {
        var (uiFees, gasFees) = CalculatePositionFeesBreakdown(position);
        return uiFees + gasFees;
    }

    /// <summary>
    /// Calculates the UI and Gas fees breakdown for a position based on GMX V2 fee structure
    /// </summary>
    /// <param name="position">The position to calculate fees for</param>
    /// <returns>A tuple containing (uiFees, gasFees)</returns>
    public static (decimal uiFees, decimal gasFees) CalculatePositionFeesBreakdown(Position position)
    {
        decimal uiFees = 0;
        decimal gasFees = 0;

        if (position?.Open?.Price <= 0 || position?.Open?.Quantity <= 0)
        {
            return (uiFees, gasFees); // Return 0 if position data is invalid
        }

        // Calculate position size in USD (leverage is already included in quantity calculation)
        var positionSizeUsd = (position.Open.Price * position.Open.Quantity) * position.Open.Leverage;

        // UI Fee: 0.1% of position size paid on opening
        var uiFeeOpen = positionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on opening
        uiFees += uiFeeOpen;

        // UI Fee: 0.1% of position size paid on closing - only if position was actually closed
        // Check which closing trade was executed (StopLoss, TakeProfit1, or TakeProfit2)
        if (position.StopLoss?.Status == TradeStatus.Filled)
        {
            var stopLossPositionSizeUsd = (position.StopLoss.Price * position.StopLoss.Quantity) * position.StopLoss.Leverage;
            var uiFeeClose = stopLossPositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via StopLoss
            uiFees += uiFeeClose;
        }
        else if (position.TakeProfit1?.Status == TradeStatus.Filled)
        {
            var takeProfit1PositionSizeUsd = (position.TakeProfit1.Price * position.TakeProfit1.Quantity) * position.TakeProfit1.Leverage;
            var uiFeeClose = takeProfit1PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit1
            uiFees += uiFeeClose;
        }
        else if (position.TakeProfit2?.Status == TradeStatus.Filled)
        {
            var takeProfit2PositionSizeUsd = (position.TakeProfit2.Price * position.TakeProfit2.Quantity) * position.TakeProfit2.Leverage;
            var uiFeeClose = takeProfit2PositionSizeUsd * Constants.GMX.Config.UiFeeRate; // Fee paid on closing via TakeProfit2
            uiFees += uiFeeClose;
        }

        // Gas Fee: $0.15 for opening position only
        // Closing is handled by oracle, so no gas fee for closing
        gasFees += Constants.GMX.Config.GasFeePerTransaction;

        return (uiFees, gasFees);
    }

    /// <summary>
    /// Calculates UI fees for opening a position
    /// </summary>
    /// <param name="positionSizeUsd">The position size in USD</param>
    /// <returns>The UI fees for opening</returns>
    public static decimal CalculateOpeningUiFees(decimal positionSizeUsd)
    {
        return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
    }

    /// <summary>
    /// Calculates UI fees for closing a position
    /// </summary>
    /// <param name="positionSizeUsd">The position size in USD</param>
    /// <returns>The UI fees for closing</returns>
    public static decimal CalculateClosingUiFees(decimal positionSizeUsd)
    {
        return positionSizeUsd * Constants.GMX.Config.UiFeeRate;
    }

    /// <summary>
    /// Calculates gas fees for opening a position
    /// </summary>
    /// <returns>The gas fees for opening (fixed at $0.15)</returns>
    public static decimal CalculateOpeningGasFees()
    {
        return Constants.GMX.Config.GasFeePerTransaction;
    }

     /// <summary>
    /// Calculates the total volume for a position based on its status and filled trades
    /// </summary>
    /// <param name="position">The position to calculate volume for</param>
    /// <returns>The total volume for the position</returns>
    public static decimal GetVolumeForPosition(Position position)
    {
        // Always include the opening trade volume
        var totalVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;

        // For closed positions, add volume from filled closing trades
        if (position.IsFinished())
        {
            // Add Stop Loss volume if filled
            if (position.StopLoss?.Status == TradeStatus.Filled)
            {
                totalVolume += position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
            }

            // Add Take Profit 1 volume if filled
            if (position.TakeProfit1?.Status == TradeStatus.Filled)
            {
                totalVolume += position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
            }

            // Add Take Profit 2 volume if filled
            if (position.TakeProfit2?.Status == TradeStatus.Filled)
            {
                totalVolume += position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
            }
        }

        return totalVolume;
    }
}