using cAlgo.API;
using cAlgo.API.Indicators;
using System;
using System.Linq;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class KeltnerSlopeTickBot : Robot
    {
        [Parameter("HMA Period", DefaultValue = 250, Group = "Indicators")]
        public int HmaPeriod { get; set; }

        [Parameter("SMA Period", DefaultValue = 200, Group = "Indicators")]
        public int SmaPeriod { get; set; }

        [Parameter("KC Centerline Period", DefaultValue = 20, Group = "Indicators")]
        public int KeltnerSmaPeriod { get; set; }

        [Parameter("KC ATR Period", DefaultValue = 20, Group = "Indicators")]
        public int KeltnerAtrPeriod { get; set; }

        [Parameter("KC ATR MA Type", DefaultValue = MovingAverageType.Hull, Group = "Indicators")]
        public MovingAverageType KeltnerAtrMaType { get; set; }

        [Parameter("KC Band Distance", DefaultValue = 2.0, Group = "Indicators")]
        public double KeltnerBandDistance { get; set; }

        [Parameter("Equity Risk %", DefaultValue = 1.0, Group = "Trading")]
        public double EquityRiskPercent { get; set; }

        [Parameter("Max Open Positions", DefaultValue = 10, Group = "Trading")]
        public int MaxPositions { get; set; }

        [Parameter("Drawing History (Bars)", DefaultValue = 500, Group = "Display")]
        public int DrawingHistory { get; set; }

        private HullMovingAverage _hma;
        private SimpleMovingAverage _sma;
        private KeltnerChannels _keltnerChannels;
        
        // KORREKTUR: Variablen zum Speichern des vorherigen Tick-Preises.
        private double _previousAsk;
        private double _previousBid;

        private const string BotLabelPrefix = "KeltnerBot";
        private const string HmaLinePrefix = "Bot_HMA";
        private const string SmaLinePrefix = "Bot_SMA";
        private const string KcTopLinePrefix = "Bot_KC_Top";
        private const string KcMainLinePrefix = "Bot_KC_Main";
        private const string KcBottomLinePrefix = "Bot_KC_Bottom";

        protected override void OnStart()
        {
            _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod);
            _sma = Indicators.SimpleMovingAverage(Bars.ClosePrices, SmaPeriod);
            _keltnerChannels = Indicators.KeltnerChannels(KeltnerSmaPeriod, MovingAverageType.Simple, KeltnerAtrPeriod, KeltnerAtrMaType, KeltnerBandDistance);
            
            Positions.Opened += OnPositionOpened;

            // KORREKTUR: Initialisiert die Preis-Variablen, um eine falsche Auslösung beim ersten Tick zu vermeiden.
            _previousAsk = Symbol.Ask;
            _previousBid = Symbol.Bid;
        }

        protected override void OnStop()
        {
            Chart.RemoveAllObjects();
        }

        protected override void OnBar()
        {
            DrawIndicators();
            RemoveOldSegments();
        }

        protected override void OnTick()
        {
            if (Positions.Count >= MaxPositions)
            {
                return;
            }

            var hmaSlope = _hma.Result.Last(1) - _hma.Result.Last(2);
            var smaSlope = _sma.Result.Last(1) - _sma.Result.Last(2);
            var keltnerMainSlope = _keltnerChannels.Main.Last(1) - _keltnerChannels.Main.Last(2);
            
            var kcMain = _keltnerChannels.Main.Last(1);

            // Long-Entry-Logik
            bool isUpTrend = hmaSlope > 0 && smaSlope > 0;
            // KORREKTUR: Verwendet den gespeicherten _previousAsk für einen echten Tick-basierten Kreuzungsvergleich.
            bool isBuyCross = _previousAsk <= kcMain && Symbol.Ask > kcMain;

            if (isUpTrend && isBuyCross)
            {
                var stopLossPrice = _keltnerChannels.Bottom.Last(1);
                var takeProfitPrice = _keltnerChannels.Top.Last(1);
                
                if (stopLossPrice >= Symbol.Ask) 
                {
                    UpdatePreviousPrices();
                    return;
                }
                
                var stopLossInPips = (Symbol.Ask - stopLossPrice) / Symbol.PipSize;
                var takeProfitInPips = (takeProfitPrice - Symbol.Ask) / Symbol.PipSize;

                double? volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, EquityRiskPercent / MaxPositions / 100, stopLossPrice);
                if (volumeInUnits.HasValue)
                {
                    var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits.Value, RoundingMode.Down);
                    if (normalizedVolume > 0)
                    {
                        ExecuteMarketOrder(TradeType.Buy, SymbolName, normalizedVolume, $"{BotLabelPrefix}_Buy", stopLossInPips, takeProfitInPips);
                    }
                }
            }

            // Short-Entry-Logik
            bool isDownTrend = hmaSlope < 0 && smaSlope < 0;
            // KORREKTUR: Verwendet den gespeicherten _previousBid für einen echten Tick-basierten Kreuzungsvergleich.
            bool isSellCross = _previousBid >= kcMain && Symbol.Bid < kcMain;

            if (isDownTrend && isSellCross)
            {
                var stopLossPrice = _keltnerChannels.Top.Last(1);
                var takeProfitPrice = _keltnerChannels.Bottom.Last(1);

                if (stopLossPrice <= Symbol.Bid)
                {
                    UpdatePreviousPrices();
                    return;
                }
                
                var stopLossInPips = (stopLossPrice - Symbol.Bid) / Symbol.PipSize;
                var takeProfitInPips = (Symbol.Bid - takeProfitPrice) / Symbol.PipSize;

                double? volumeInUnits = Symbol.VolumeForProportionalRisk(ProportionalAmountType.Equity, EquityRiskPercent / MaxPositions / 100, stopLossPrice);
                if (volumeInUnits.HasValue)
                {
                    var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeInUnits.Value, RoundingMode.Down);
                    if (normalizedVolume > 0)
                    {
                        ExecuteMarketOrder(TradeType.Sell, SymbolName, normalizedVolume, $"{BotLabelPrefix}_Sell", stopLossInPips, takeProfitInPips);
                    }
                }
            }

            // KORREKTUR: Speichert die aktuellen Preise für den nächsten Tick.
            UpdatePreviousPrices();
        }

        private void UpdatePreviousPrices()
        {
            _previousAsk = Symbol.Ask;
            _previousBid = Symbol.Bid;
        }
        
        private void OnPositionOpened(PositionOpenedEventArgs args)
        {
            var position = args.Position;
            
            if (!position.Label.StartsWith(BotLabelPrefix))
            {
                return;
            }

            if (!position.StopLoss.HasValue || !position.TakeProfit.HasValue)
            {
                Print("KRITISCHER FEHLER: Position {0} ({1}) wurde ohne StopLoss oder TakeProfit eröffnet. Bot wird gestoppt.", position.Id, position.Label);
                Stop();
            }
        }

        private void DrawIndicators()
        {
            // Da Indikatorwerte sich nur auf Bar-Basis ändern, ist eine Aktualisierung pro Tick nicht nötig.
            // Die Ausführung im OnBar spart daher Rechenleistung.
            var previousBarTime = Bars.OpenTimes.Last(2);
            var currentBarTime = Bars.OpenTimes.Last(1);
            string uniqueSuffix = $"_{currentBarTime:o}";

            Chart.DrawTrendLine(HmaLinePrefix + uniqueSuffix, previousBarTime, _hma.Result.Last(2), currentBarTime, _hma.Result.Last(1), Color.Purple);
            Chart.DrawTrendLine(SmaLinePrefix + uniqueSuffix, previousBarTime, _sma.Result.Last(2), currentBarTime, _sma.Result.Last(1), Color.Orange);
            Chart.DrawTrendLine(KcTopLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Top.Last(2), currentBarTime, _keltnerChannels.Top.Last(1), Color.Blue);
            Chart.DrawTrendLine(KcMainLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Main.Last(2), currentBarTime, _keltnerChannels.Main.Last(1), Color.Blue, 2, LineStyle.Dots);
            Chart.DrawTrendLine(KcBottomLinePrefix + uniqueSuffix, previousBarTime, _keltnerChannels.Bottom.Last(2), currentBarTime, _keltnerChannels.Bottom.Last(1), Color.Blue);
        }

        private void RemoveOldSegments()
        {
            if (Bars.Count <= DrawingHistory)
            {
                return;
            }

            var oldBarTime = Bars.OpenTimes.Last(DrawingHistory);
            string oldUniqueSuffix = $"_{oldBarTime:o}";

            Chart.RemoveObject(HmaLinePrefix + oldUniqueSuffix);
            Chart.RemoveObject(SmaLinePrefix + oldUniqueSuffix);
            Chart.RemoveObject(KcTopLinePrefix + oldUniqueSuffix);
            Chart.RemoveObject(KcMainLinePrefix + oldUniqueSuffix);
            Chart.RemoveObject(KcBottomLinePrefix + oldUniqueSuffix);
        }
    }
}