using Managing.Core;
using Managing.Domain.Indicators;
using Managing.Domain.Shared.Rules;
using Managing.Domain.Strategies.Base;
using Skender.Stock.Indicators;
using static Managing.Common.Enums;
using Candle = Managing.Domain.Candles.Candle;

namespace Managing.Domain.Strategies.Signals;

public class RsiDivergenceIndicatorBase : IndicatorBase
{
    public List<LightSignal> Signals { get; set; }
    public TradeDirection Direction { get; set; }
    private const int UpperBand = 70;
    private const int LowerBand = 30;

    public RsiDivergenceIndicatorBase(string name, int period) : base(name, IndicatorType.RsiDivergence)
    {
        Period = period;
        Signals = new List<LightSignal>();
    }

    /// <summary>
    /// Get RSI signals
    /// </summary>
    /// <returns></returns>
    public override List<LightSignal> Run(HashSet<Candle> candles)
    {
        if (!Period.HasValue || candles.Count <= Period)
        {
            return null;
        }

        var ticker = candles.First().Ticker;

        try
        {
            var rsiResult = candles.TakeLast(10 * Period.Value).GetRsi(Period.Value).ToList();
            var candlesRsi = MapRsiToCandle(rsiResult, candles.TakeLast(10 * Period.Value));

            if (candlesRsi.Count(c => c.Rsi > 0) == 0)
                return null;

            GetLongSignals(candlesRsi, candles);
            GetShortSignals(candlesRsi, candles);

            return Signals;
        }
        catch (RuleException)
        {
            return null;
        }
    }

    public override IndicatorsResultBase GetIndicatorValues(HashSet<Candle> candles)
    {
        return new IndicatorsResultBase()
        {
            Rsi = candles.GetRsi(Period.Value).ToList()
        };
    }

    private void GetLongSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
    {
        // Set the low and high for first candle
        var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);
        var highPrices = new List<CandleRsi>();
        var lowPrices = new List<CandleRsi>();

        var highRsi = new List<CandleRsi>();
        var lowRsi = new List<CandleRsi>();

        highPrices.Add(firstCandleRsi);
        lowPrices.Add(firstCandleRsi);

        highRsi.Add(firstCandleRsi);
        lowRsi.Add(firstCandleRsi);

        var previousCandle = firstCandleRsi;

        // For a long 
        foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
        {
            // If price go down
            if (previousCandle.Close > currentCandle.Close)
            {
                // because the last price is upper than the current
                highPrices.AddItem(previousCandle);

                // Check if rsi is higher than the last lowest
                if (currentCandle.Rsi > lowRsi.TakeLast(Period.Value).Min(r => r.Rsi))
                {
                    // If new higher high, we set it
                    if (currentCandle.Rsi > highRsi.Last().Rsi)
                        highRsi.AddItem(currentCandle);

                    if (currentCandle.Rsi > lowRsi.Last().Rsi)
                        lowRsi.AddItem(currentCandle);

                    // Price go down but RSI go up
                    if (currentCandle.Close < lowPrices.TakeLast(Period.Value).Min(p => p.Close))
                    {
                        AddSignal(currentCandle, TradeDirection.Long, candles);
                    }
                }
                else
                {
                    // No divergence, price go down, rsi go down
                    lowRsi.AddItem(currentCandle);
                }

                lowPrices.AddItem(currentCandle);
            }
            else
            {
                // Price go up, so we have to update if price is a new higher high than previous candle
                // Normally always true
                if (previousCandle.Close < currentCandle.Close)
                    highPrices.AddItem(currentCandle); //15-15-12-14-17

                // If rsi is lower low or not set
                if (currentCandle.Rsi < lowRsi.Last().Rsi || lowRsi.Last().Rsi == 0)
                    lowRsi.AddItem(currentCandle);

                // Price going up, so if its a new high we set it
                if (currentCandle.Rsi > highRsi.Last().Rsi)
                    highRsi.AddItem(currentCandle);
            }

            previousCandle = currentCandle;
        }
    }

    private void GetShortSignals(List<CandleRsi> candlesRsi, HashSet<Candle> candles)
    {
        // Set the low and high for first candle
        var firstCandleRsi = candlesRsi.First(c => c.Rsi > 0);

        var signals = new List<Signal>();
        var highPrices = new List<CandleRsi>();
        var lowPrices = new List<CandleRsi>();

        var highRsi = new List<CandleRsi>();
        var lowRsi = new List<CandleRsi>();

        highPrices.Add(firstCandleRsi);
        lowPrices.Add(firstCandleRsi);

        highRsi.Add(firstCandleRsi);
        lowRsi.Add(firstCandleRsi);

        var previousCandle = firstCandleRsi;

        // For a short 
        foreach (var currentCandle in candlesRsi.FindAll(r => r.Rsi > 0).Skip(1))
        {
            // If price go up
            if (previousCandle.Close < currentCandle.Close)
            {
                // because the last price is lower than the current
                lowPrices.AddItem(previousCandle);

                // Check if rsi is lower than the last high
                if (currentCandle.Rsi < highRsi.TakeLast(Period.Value).Max(r => r.Rsi))
                {
                    // If new lower low, we set it
                    if (currentCandle.Rsi < lowRsi.Last().Rsi)
                        lowRsi.AddItem(currentCandle);

                    if (currentCandle.Rsi < highRsi.Last().Rsi)
                        highRsi.AddItem(currentCandle);

                    // Price go up but RSI go down
                    if (currentCandle.Close > highPrices.TakeLast(Period.Value).Max(p => p.Close))
                    {
                        AddSignal(currentCandle, TradeDirection.Short, candles);
                    }
                }
                else
                {
                    // No divergence, price go up, rsi go up
                    highRsi.AddItem(currentCandle);
                }

                highPrices.AddItem(currentCandle);
            }
            else
            {
                // Price go down, so we have to update if price is a new lower low than previous candle
                if (previousCandle.Close > currentCandle.Close)
                    lowPrices.AddItem(currentCandle);

                // If rsi is higher high or not set
                if (currentCandle.Rsi > highRsi.Last().Rsi || highRsi.Last().Rsi == 0)
                    highRsi.AddItem(currentCandle);

                // Price going down, so if its a new low we set it
                if (currentCandle.Rsi < lowRsi.Last().Rsi)
                    lowRsi.AddItem(currentCandle);
            }

            previousCandle = currentCandle;
        }
    }

    private void AddSignal(CandleRsi candleSignal, TradeDirection direction, HashSet<Candle> candles)
    {
        var signal = new LightSignal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, Confidence.Low,
            candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);

        if (Signals.Count(s => s.Identifier == signal.Identifier) < 1)
        {
            var lastCandleOnPeriod = candles.TakeLast(Period.Value).ToList();
            var signalsOnPeriod = Signals.Where(s => s.Date >= lastCandleOnPeriod[0].Date).ToList();

            if (signalsOnPeriod.Count == 1)
                signal.SetConfidence(Confidence.Medium);

            if (signalsOnPeriod.Count >= 2)
                signal.SetConfidence(Confidence.High);

            Signals.AddItem(signal);
        }
    }

    private List<CandleRsi> MapRsiToCandle(IReadOnlyCollection<RsiResult> rsiResult,
        IEnumerable<Candle> candles)
    {
        return candles.Select(c => new CandleRsi()
        {
            Close = c.Close,
            Rsi = rsiResult.Find(c.Date).Rsi.GetValueOrDefault(),
            Date = c.Date,
            Ticker = c.Ticker,
            Exchange = c.Exchange
        }).ToList();
    }

    private class CandleRsi : Candle
    {
        public double Rsi { get; set; }
    }
}