using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using static Managing.Common.Enums;

namespace Managing.Domain.Shared.Helpers;

public static class TradingBox
{
    public static Signal GetSignal(HashSet<Candle> newCandles, HashSet<IStrategy> strategies,
        HashSet<Signal> previousSignal, int? loopbackPeriod = 1)
    {
        var signalOnCandles = new HashSet<Signal>();
        var limitedCandles = newCandles.ToList().TakeLast(600).ToList();
        foreach (var strategy in strategies)
        {
            strategy.UpdateCandles(limitedCandles.ToHashSet());
            var signals = strategy.Run();

            if (signals == null || signals.Count == 0) continue;

            // Ensure limitedCandles is ordered chronologically
            var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList();

            var loopback = loopbackPeriod.HasValue && loopbackPeriod > 1 ? loopbackPeriod.Value : 1;
            var candleLoopback = orderedCandles.TakeLast(loopback).ToList();

            if (!candleLoopback.Any())
            {
                // Handle empty case (e.g., log warning, skip processing)
                continue;
            }

            var loopbackStartDate = candleLoopback.First().Date;

            foreach (var signal in signals.Where(s => s.Date >= loopbackStartDate))
            {
                var hasExistingSignal = previousSignal.Any(s => s.Identifier == signal.Identifier);
                if (!hasExistingSignal)
                {
                    bool shouldAdd = previousSignal.Count == 0 || previousSignal.Last().Date < signal.Date;
                    if (shouldAdd)
                    {
                        signalOnCandles.Add(signal);
                    }
                }
            }
        }

        if (signalOnCandles.Count != strategies.Count)
            return null;

        var data = newCandles.First();
        return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
            data.Timeframe);
    }

    public static Signal ComputeSignals(HashSet<IStrategy> strategies, HashSet<Signal> signalOnCandles, Ticker ticker,
        Timeframe timeframe)
    {
        Signal signal = null;
        if (strategies.Count > 1)
        {
            var trendSignal = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
            var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
            var contextStrategiesCount = strategies.Count(s => s.SignalType == SignalType.Context);
            var validContext = true;

            if (contextStrategiesCount > 0 &&
                signalOnCandles.Count(s => s.SignalType == SignalType.Context) != contextStrategiesCount)
            {
                validContext = false;
            }

            if (signals.All(s => s.Direction == TradeDirection.Long) &&
                trendSignal.All(t => t.Direction == TradeDirection.Long) && validContext)
            {
                signal = new Signal(
                    ticker,
                    TradeDirection.Long,
                    Confidence.High,
                    signals.Last().Candle,
                    signals.Last().Date,
                    signals.Last().Exchange,
                    StrategyType.Composite, SignalType.Signal);
            }
            else if (signals.All(s => s.Direction == TradeDirection.Short) &&
                     trendSignal.All(t => t.Direction == TradeDirection.Short) && validContext)
            {
                signal = new Signal(
                    ticker,
                    TradeDirection.Short,
                    Confidence.High,
                    signals.Last().Candle,
                    signals.Last().Date,
                    signals.Last().Exchange,
                    StrategyType.Composite, SignalType.Signal);
            }
        }
        else
        {
            // Only one strategy, we just add the single signal to the bot
            signal = signalOnCandles.Single();
        }

        return signal;
    }

    public static MoneyManagement GetBestMoneyManagement(List<Candle> candles, List<Position> positions,
        MoneyManagement originMoneyManagement)
    {
        // Foreach positions, identitify the price when the position is open
        // Then, foreach candles, get the maximum price before the next position
        // Then, identify the lowest price before the maximum price 
        // Base on that, return the best StopLoss and TakeProfit to use and build a 
        var moneyManagement = new MoneyManagement();
        var stoplossPercentage = new List<decimal>();
        var takeProfitsPercentage = new List<decimal>();

        if (positions.Count == 0)
            return null;

        for (var i = 0; i < positions.Count; i++)
        {
            var position = positions[i];
            var nextPosition = i + 1 < positions.Count ? positions[i + 1] : null;
            var (stopLoss, takeProfit) = GetBestSltpForPosition(candles, position, nextPosition);

            stoplossPercentage.Add(stopLoss);
            takeProfitsPercentage.Add(takeProfit);
        }

        moneyManagement.StopLoss = stoplossPercentage.Average();
        moneyManagement.TakeProfit = takeProfitsPercentage.Average();
        moneyManagement.Timeframe = originMoneyManagement.Timeframe;
        moneyManagement.Leverage = originMoneyManagement.Leverage;
        moneyManagement.Name = "Optimized";
        return moneyManagement;
    }

    public static (decimal Stoploss, decimal TakeProfit) GetBestSltpForPosition(List<Candle> candles, Position position,
        Position nextPosition)
    {
        var stopLoss = 0M;
        var takeProfit = 0M;
        var candlesBeforeNextPosition = candles.Where(c =>
                c.Date >= position.Date && c.Date <= (nextPosition == null ? candles.Last().Date : nextPosition.Date))
            .ToList();

        if (position.OriginDirection == TradeDirection.Long)
        {
            var maxPrice = candlesBeforeNextPosition.Max(c => c.High);
            var minPrice = candlesBeforeNextPosition.TakeWhile(c => c.High <= maxPrice).Min(c => c.Low);
            stopLoss = GetPercentageFromEntry(position.Open.Price, minPrice);
            takeProfit = GetPercentageFromEntry(position.Open.Price, maxPrice);
        }
        else if (position.OriginDirection == TradeDirection.Short)
        {
            var minPrice = candlesBeforeNextPosition.Min(c => c.Low);
            var maxPrice = candlesBeforeNextPosition.TakeWhile(c => c.Low >= minPrice).Max(c => c.High);
            stopLoss = GetPercentageFromEntry(position.Open.Price, maxPrice);
            takeProfit = GetPercentageFromEntry(position.Open.Price, minPrice);
        }

        return (stopLoss, takeProfit);
    }

    private static decimal GetPercentageFromEntry(decimal entry, decimal price)
    {
        return Math.Abs(100 - ((100 * price) / entry));
    }

    public static ProfitAndLoss GetProfitAndLoss(Position position, decimal quantity, decimal price, decimal leverage)
    {
        var orders = new List<Tuple<decimal, decimal>>
        {
            new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
            new Tuple<decimal, decimal>(-quantity, price)
        };


        var pnl = new ProfitAndLoss(orders, position.OriginDirection);
        // Apply leverage on the realized pnl
        pnl.Realized = pnl.Realized * leverage;
        return pnl;
    }

    /// <summary>
    /// Calculates the total volume traded across all positions
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <returns>The total volume traded in decimal</returns>
    public static decimal GetTotalVolumeTraded(List<Position> positions)
    {
        decimal totalVolume = 0;

        foreach (var position in positions)
        {
            // Add entry volume
            totalVolume += position.Open.Quantity * position.Open.Price;

            // Add exit volumes from stop loss or take profits if they were executed
            if (position.StopLoss.Status == TradeStatus.Filled)
            {
                totalVolume += position.StopLoss.Quantity * position.StopLoss.Price;
            }

            if (position.TakeProfit1.Status == TradeStatus.Filled)
            {
                totalVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price;
            }

            if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled)
            {
                totalVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price;
            }
        }

        return totalVolume;
    }

    /// <summary>
    /// Calculates the volume traded in the last 24 hours
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <returns>The volume traded in the last 24 hours in decimal</returns>
    public static decimal GetLast24HVolumeTraded(List<Position> positions)
    {
        decimal last24hVolume = 0;
        DateTime cutoff = DateTime.UtcNow.AddHours(-24);

        foreach (var position in positions)
        {
            // Check if any part of this position was traded in the last 24 hours

            // Add entry volume if it was within the last 24 hours
            if (position.Open.Date >= cutoff)
            {
                last24hVolume += position.Open.Quantity * position.Open.Price;
            }

            // Add exit volumes if they were executed within the last 24 hours
            if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff)
            {
                last24hVolume += position.StopLoss.Quantity * position.StopLoss.Price;
            }

            if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff)
            {
                last24hVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price;
            }

            if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
                position.TakeProfit2.Date >= cutoff)
            {
                last24hVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price;
            }
        }

        return last24hVolume;
    }

    /// <summary>
    /// Gets the win/loss counts from positions
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <returns>A tuple containing (wins, losses)</returns>
    public static (int Wins, int Losses) GetWinLossCount(List<Position> positions)
    {
        int wins = 0;
        int losses = 0;

        foreach (var position in positions)
        {
            // Only count finished positions
            if (position.IsFinished())
            {
                if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
                {
                    wins++;
                }
                else
                {
                    losses++;
                }
            }
        }

        return (wins, losses);
    }

    /// <summary>
    /// Calculates the ROI for the last 24 hours
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <returns>The ROI for the last 24 hours as a percentage</returns>
    public static decimal GetLast24HROI(List<Position> positions)
    {
        decimal profitLast24h = 0;
        decimal investmentLast24h = 0;
        DateTime cutoff = DateTime.UtcNow.AddHours(-24);

        foreach (var position in positions)
        {
            // Only count positions that were opened or closed within the last 24 hours
            if (position.IsFinished() &&
                (position.Open.Date >= cutoff ||
                 (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) ||
                 (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff) ||
                 (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
                  position.TakeProfit2.Date >= cutoff)))
            {
                profitLast24h += position.ProfitAndLoss != null ? position.ProfitAndLoss.Realized : 0;
                investmentLast24h += position.Open.Quantity * position.Open.Price;
            }
        }

        // Avoid division by zero
        if (investmentLast24h == 0)
            return 0;

        return (profitLast24h / investmentLast24h) * 100;
    }

    /// <summary>
    /// Calculates profit and loss for positions within a specific time range
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
    /// <returns>The PnL for positions in the specified range</returns>
    public static decimal GetPnLInTimeRange(List<Position> positions, string timeFilter)
    {
        // If Total, just return the total PnL
        if (timeFilter == "Total")
        {
            return positions
                .Where(p => p.IsFinished() && p.ProfitAndLoss != null)
                .Sum(p => p.ProfitAndLoss.Realized);
        }

        // Convert time filter to a DateTime
        DateTime cutoffDate = DateTime.UtcNow;

        switch (timeFilter)
        {
            case "24H":
                cutoffDate = DateTime.UtcNow.AddHours(-24);
                break;
            case "3D":
                cutoffDate = DateTime.UtcNow.AddDays(-3);
                break;
            case "1W":
                cutoffDate = DateTime.UtcNow.AddDays(-7);
                break;
            case "1M":
                cutoffDate = DateTime.UtcNow.AddMonths(-1);
                break;
            case "1Y":
                cutoffDate = DateTime.UtcNow.AddYears(-1);
                break;
        }

        // Include positions that were closed within the time range
        return positions
            .Where(p => p.IsFinished() && p.ProfitAndLoss != null &&
                        (p.Date >= cutoffDate ||
                         (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
                         (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
                         (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
                          p.TakeProfit2.Date >= cutoffDate)))
            .Sum(p => p.ProfitAndLoss.Realized);
    }

    /// <summary>
    /// Calculates ROI for positions within a specific time range
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
    /// <returns>The ROI as a percentage for positions in the specified range</returns>
    public static decimal GetROIInTimeRange(List<Position> positions, string timeFilter)
    {
        // If no positions, return 0
        if (!positions.Any())
        {
            return 0;
        }

        // Convert time filter to a DateTime
        DateTime cutoffDate = DateTime.UtcNow;

        if (timeFilter != "Total")
        {
            switch (timeFilter)
            {
                case "24H":
                    cutoffDate = DateTime.UtcNow.AddHours(-24);
                    break;
                case "3D":
                    cutoffDate = DateTime.UtcNow.AddDays(-3);
                    break;
                case "1W":
                    cutoffDate = DateTime.UtcNow.AddDays(-7);
                    break;
                case "1M":
                    cutoffDate = DateTime.UtcNow.AddMonths(-1);
                    break;
                case "1Y":
                    cutoffDate = DateTime.UtcNow.AddYears(-1);
                    break;
            }
        }

        // Filter positions in the time range
        var filteredPositions = timeFilter == "Total"
            ? positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null)
            : positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null &&
                                   (p.Date >= cutoffDate ||
                                    (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
                                    (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
                                    (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
                                     p.TakeProfit2.Date >= cutoffDate)));

        // Calculate investment and profit
        decimal totalInvestment = filteredPositions.Sum(p => p.Open.Quantity * p.Open.Price);
        decimal totalProfit = filteredPositions.Sum(p => p.ProfitAndLoss.Realized);

        // Calculate ROI
        if (totalInvestment == 0)
        {
            return 0;
        }

        return (totalProfit / totalInvestment) * 100;
    }

    /// <summary>
    /// Gets the win/loss counts from positions in a specific time range
    /// </summary>
    /// <param name="positions">List of positions to analyze</param>
    /// <param name="timeFilter">Time filter to apply (24H, 3D, 1W, 1M, 1Y, Total)</param>
    /// <returns>A tuple containing (wins, losses)</returns>
    public static (int Wins, int Losses) GetWinLossCountInTimeRange(List<Position> positions, string timeFilter)
    {
        // Convert time filter to a DateTime
        DateTime cutoffDate = DateTime.UtcNow;

        if (timeFilter != "Total")
        {
            switch (timeFilter)
            {
                case "24H":
                    cutoffDate = DateTime.UtcNow.AddHours(-24);
                    break;
                case "3D":
                    cutoffDate = DateTime.UtcNow.AddDays(-3);
                    break;
                case "1W":
                    cutoffDate = DateTime.UtcNow.AddDays(-7);
                    break;
                case "1M":
                    cutoffDate = DateTime.UtcNow.AddMonths(-1);
                    break;
                case "1Y":
                    cutoffDate = DateTime.UtcNow.AddYears(-1);
                    break;
            }
        }

        // Filter positions in the time range
        var filteredPositions = timeFilter == "Total"
            ? positions.Where(p => p.IsFinished())
            : positions.Where(p => p.IsFinished() &&
                                   (p.Date >= cutoffDate ||
                                    (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
                                    (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
                                    (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
                                     p.TakeProfit2.Date >= cutoffDate)));

        int wins = 0;
        int losses = 0;

        foreach (var position in filteredPositions)
        {
            if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
            {
                wins++;
            }
            else
            {
                losses++;
            }
        }

        return (wins, losses);
    }
}