using System;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System.Collections.Generic;
using System.Linq;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None, AddIndicators = true)]
    public class MultiTimeframeIndicatorBot : Robot
    {
        #region Identity & Parameters
        private const string BotLabel = "MultiTimeframeIndicatorBot_v1";

        [Parameter("Fast MA Period", DefaultValue = 250, Group = "Periods")]
        public int FastMaPeriod { get; set; }

        [Parameter("Slow MA Period", DefaultValue = 200, Group = "Periods")]
        public int SlowMaPeriod { get; set; }

        [Parameter("Swing MA Period", DefaultValue = 20, Group = "Periods")]
        public int SwingMaPeriod { get; set; }

        [Parameter("Max Risk", DefaultValue = 100.0, Group = "Risk")]
        public double MaxRisk { get; set; }

        [Parameter("Min Stop Loss (Pips)", DefaultValue = 10.0, Group = "Risk Management", MinValue = 1)]
        public double MinStopLossPips { get; set; }
        
        [Parameter("Take Profit % of Range", DefaultValue = 75.0, Group = "Risk Management", MinValue = 0.1)]
        public double TakeProfitPercentage { get; set; }

        [Parameter("Stop Loss % of Range", DefaultValue = 50.0, Group = "Risk Management", MinValue = 0.1)]
        public double StopLossPercentage { get; set; }
        
        [Parameter("Entry Offset % of Range", DefaultValue = 5.0, Group = "Risk Management", MinValue = 0)]
        public double EntryOffsetPercentage { get; set; }
        
        [Parameter("Pyramiding Threshold (Pips)", DefaultValue = 20.0, Group = "Pyramiding")]
        public double PyramidingThresholdPips { get; set; }

        [Parameter("Max Pyramiding Positions", DefaultValue = 3, Group = "Pyramiding")]
        public int MaxPyramidingPositions { get; set; }
        
        [Parameter("Use Trailing Stop", DefaultValue = true, Group = "Risk Management")]
        public bool UseTrailingStop { get; set; }
        #endregion

        #region Fields
        // Holds custom management data for a single trade.
        private class TradeInfo
        {
            public double StopLossPrice { get; set; }
            public double? TakeProfitPrice { get; set; }
            public double InitialStopLossPips { get; set; }
        }

        // Stores management data for all open positions, keyed by position ID.
        private readonly Dictionary<int, TradeInfo> _managedTrades = new Dictionary<int, TradeInfo>();
        private TradeInfo _pendingTradeInfo;
        
        private enum MarketStructureBias { Neutral, Bullish, Bearish }
        
        private Bars _dailyBars;
        private Color _originalColor;
        private HullMovingAverage _fastMaDaily, _swingMaDaily, _fastMaPrimary, _swingMaPrimary;
        private SimpleMovingAverage _slowMaDaily, _slowMaPrimary;
        private Stack<double> _highestHighs, _lowestLows;
        private bool isBullishBias;
        private bool isBearishBias;
        private double _hh, _ll;
        private MarketStructureBias _structureBias = MarketStructureBias.Neutral;
        #endregion

        protected override void OnStart()
        {
            _dailyBars = MarketData.GetBars(TimeFrame.Daily);
            _fastMaDaily = Indicators.HullMovingAverage(_dailyBars.ClosePrices, FastMaPeriod);
            _slowMaDaily = Indicators.SimpleMovingAverage(_dailyBars.ClosePrices, SlowMaPeriod);
            _swingMaDaily = Indicators.HullMovingAverage(_dailyBars.ClosePrices, SwingMaPeriod);
            _fastMaPrimary = Indicators.HullMovingAverage(Bars.ClosePrices, FastMaPeriod);
            _slowMaPrimary = Indicators.SimpleMovingAverage(Bars.ClosePrices, SlowMaPeriod);
            _swingMaPrimary = Indicators.HullMovingAverage(Bars.ClosePrices, SwingMaPeriod);

            _highestHighs = new Stack<double>();
            _lowestLows = new Stack<double>();
            _originalColor = Chart.ColorSettings.GridLinesColor;

            Positions.Opened += OnPositionOpened;
            Positions.Closed += OnPositionClosed;
        }

        protected override void OnStop()
        {
            Chart.ColorSettings.GridLinesColor = _originalColor;
            Positions.Opened -= OnPositionOpened;
            Positions.Closed -= OnPositionClosed;
        }

        protected override void OnTick()
        {
            ManageOpenTrades();
        }

        protected override void OnBar()
        {
            if (ManagePositions()) return;

            if (Positions.Find(BotLabel, Symbol.Name) != null) return;
            if (Bars.Count < 5) return;
            
            if (_hh == 0 || _ll == 0)
            {
                _hh = Bars.HighPrices.Last(1);
                _ll = Bars.LowPrices.Last(1);
            }

            if (Bars.HighPrices.Last(1) > _hh) _hh = Bars.HighPrices.Last(1);
            if (Bars.LowPrices.Last(1) < _ll) _ll = Bars.LowPrices.Last(1);
            
            bool isSwingLow = _swingMaPrimary.Result.Last(3) > _swingMaPrimary.Result.Last(2) && _swingMaPrimary.Result.Last(2) < _swingMaPrimary.Result.Last(1);
            bool isSwingHigh = _swingMaPrimary.Result.Last(3) < _swingMaPrimary.Result.Last(2) && _swingMaPrimary.Result.Last(2) > _swingMaPrimary.Result.Last(1);

            if (isSwingLow) { _lowestLows.Push(_ll); _hh = Bars.HighPrices.Last(1); }
            if (isSwingHigh) { _highestHighs.Push(_hh); _ll = Bars.LowPrices.Last(1); }

            var dailyIndex = _dailyBars.OpenTimes.GetIndexByTime(Bars.Last(0).OpenTime);
            if (dailyIndex < 1) return;

            isBullishBias = _fastMaDaily.Result[dailyIndex] > _fastMaDaily.Result[dailyIndex - 1] && _slowMaDaily.Result[dailyIndex] > _slowMaDaily.Result[dailyIndex - 1] && _fastMaPrimary.Result.Last(1) > _fastMaPrimary.Result.Last(2) && _slowMaPrimary.Result.Last(1) > _slowMaPrimary.Result.Last(2);
            isBearishBias = _fastMaDaily.Result[dailyIndex] < _fastMaDaily.Result[dailyIndex - 1] && _slowMaDaily.Result[dailyIndex] < _slowMaDaily.Result[dailyIndex - 1] && _fastMaPrimary.Result.Last(1) < _fastMaPrimary.Result.Last(2) && _slowMaPrimary.Result.Last(1) < _slowMaPrimary.Result.Last(2);

            VisualizeBias(isBullishBias, isBearishBias);

            bool buySignal = _highestHighs.Count > 0 && _lowestLows.Count > 0 && Bars.HighPrices.Last(1) > _highestHighs.Peek();
            bool sellSignal = _highestHighs.Count > 0 && _lowestLows.Count > 0 && Bars.LowPrices.Last(1) < _lowestLows.Peek();

            if (buySignal) _structureBias = MarketStructureBias.Bullish;
            else if (sellSignal) _structureBias = MarketStructureBias.Bearish;

            if (isBullishBias && buySignal && _structureBias == MarketStructureBias.Bullish)
            {
                var lowPoint = _lowestLows.Peek();
                var highPoint = Math.Max(_hh, _highestHighs.Peek());
                var range = highPoint - lowPoint;
                if (range > 0)
                {
                    var entryPrice = lowPoint + range * (EntryOffsetPercentage / 100.0);
                    var stopLoss = lowPoint - range * (StopLossPercentage / 100.0);
                    double? takeProfit = UseTrailingStop ? (double?)null : lowPoint + range * (TakeProfitPercentage / 100.0);
                    
                    bool isValidOrder = (takeProfit.HasValue && takeProfit > entryPrice && entryPrice > stopLoss) || (!takeProfit.HasValue && entryPrice > stopLoss);
                    if (isValidOrder)
                    {
                        var volume = CalculateVolumeInUnits(entryPrice, stopLoss);
                        if (volume >= Symbol.VolumeInUnitsMin)
                        {
                            _pendingTradeInfo = new TradeInfo { StopLossPrice = stopLoss, TakeProfitPrice = takeProfit, InitialStopLossPips = (entryPrice - stopLoss) / Symbol.PipSize };
                            CancelAndPlaceOrder(TradeType.Buy, entryPrice, volume);
                        }
                    }
                }
            }
            else if (isBearishBias && sellSignal && _structureBias == MarketStructureBias.Bearish)
            {
                var highPoint = _highestHighs.Peek();
                var lowPoint = Math.Min(_ll, _lowestLows.Peek());
                var range = highPoint - lowPoint;
                if (range > 0)
                {
                    var entryPrice = highPoint - range * (EntryOffsetPercentage / 100.0);
                    var stopLoss = highPoint + range * (StopLossPercentage / 100.0);
                    double? takeProfit = UseTrailingStop ? (double?)null : highPoint - range * (TakeProfitPercentage / 100.0);
                    
                    bool isValidOrder = (takeProfit.HasValue && stopLoss > entryPrice && entryPrice > takeProfit) || (!takeProfit.HasValue && stopLoss > entryPrice);
                    if (isValidOrder)
                    {
                        var volume = CalculateVolumeInUnits(entryPrice, stopLoss);
                        if (volume >= Symbol.VolumeInUnitsMin)
                        {
                            _pendingTradeInfo = new TradeInfo { StopLossPrice = stopLoss, TakeProfitPrice = takeProfit, InitialStopLossPips = (stopLoss - entryPrice) / Symbol.PipSize };
                            CancelAndPlaceOrder(TradeType.Sell, entryPrice, volume);
                        }
                    }
                }
            }
            
            while (_highestHighs.Count > 0 && _highestHighs.Peek() < Bars.HighPrices.Last(1)) { _highestHighs.Pop(); }
            while (_lowestLows.Count > 0 && _lowestLows.Peek() > Bars.LowPrices.Last(1)) { _lowestLows.Pop(); }
            
            VisualizePeekValues();
        }

        private void OnPositionOpened(PositionOpenedEventArgs args)
        {
            var position = args.Position;
            if (position.Label == BotLabel && position.SymbolName == Symbol.Name)
            {
                if (_pendingTradeInfo != null && !_managedTrades.ContainsKey(position.Id))
                {
                    _managedTrades[position.Id] = _pendingTradeInfo;
                    Print($"Managing new position {position.Id} with custom SL: {_pendingTradeInfo.StopLossPrice:F5}");
                    _pendingTradeInfo = null;
                }
            }
        }

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            if (_managedTrades.Remove(args.Position.Id))
            {
                Print($"Stopped managing closed position {args.Position.Id}. Reason: {args.Reason}");
            }
        }

        private void ManageOpenTrades()
        {
            var managedPositionIds = _managedTrades.Keys.ToList();
            foreach (var positionId in managedPositionIds)
            {
                var position = Positions.FirstOrDefault(p => p.Id == positionId);
                if (position == null) 
                {
                    _managedTrades.Remove(positionId);
                    continue;
                }

                var tradeInfo = _managedTrades[positionId];
                if ((position.TradeType == TradeType.Buy && Symbol.Bid <= tradeInfo.StopLossPrice) || (position.TradeType == TradeType.Sell && Symbol.Ask >= tradeInfo.StopLossPrice))
                {
                    ClosePosition(position);
                    Print($"Position {position.Id} hit custom SL at {tradeInfo.StopLossPrice:F5}. Closing.");
                    continue;
                }

                if (!UseTrailingStop && tradeInfo.TakeProfitPrice.HasValue)
                {
                    if ((position.TradeType == TradeType.Buy && Symbol.Bid >= tradeInfo.TakeProfitPrice.Value) || (position.TradeType == TradeType.Sell && Symbol.Ask <= tradeInfo.TakeProfitPrice.Value))
                    {
                        ClosePosition(position);
                        Print($"Position {position.Id} hit custom TP at {tradeInfo.TakeProfitPrice.Value:F5}. Closing.");
                        continue;
                    }
                }
                
                if (UseTrailingStop)
                {
                    double newStopLossPrice;
                    if (position.TradeType == TradeType.Buy)
                    {
                        newStopLossPrice = Symbol.Bid - tradeInfo.InitialStopLossPips * Symbol.PipSize;
                        if (newStopLossPrice > tradeInfo.StopLossPrice)
                        {
                            tradeInfo.StopLossPrice = newStopLossPrice;
                        }
                    }
                    else // Sell
                    {
                        newStopLossPrice = Symbol.Ask + tradeInfo.InitialStopLossPips * Symbol.PipSize;
                        if (newStopLossPrice < tradeInfo.StopLossPrice)
                        {
                            tradeInfo.StopLossPrice = newStopLossPrice;
                        }
                    }
                }
            }
        }
        
        private void CancelAndPlaceOrder(TradeType tradeType, double entry, double volumeInUnits)
        {
            var existingOrder = PendingOrders.FirstOrDefault(o => o.Label == BotLabel && o.SymbolName == Symbol.Name);
            if (existingOrder != null)
            {
                CancelPendingOrder(existingOrder);
            }
            PlaceLimitOrder(tradeType, Symbol.Name, volumeInUnits, entry, BotLabel, null, null, ProtectionType.Absolute);
        }

        private double CalculateVolumeInUnits(double entryPrice, double stopLossPrice)
        {
            var stopLossInPips = Math.Abs(entryPrice - stopLossPrice) / Symbol.PipSize;
            
            if (stopLossInPips < MinStopLossPips)
            {
                Print($"Trade volume calculation skipped. Calculated SL of {stopLossInPips:F2} pips is below the minimum of {MinStopLossPips} pips.");
                return 0;
            }

            if (stopLossInPips <= 0) return 0;
            
            var volume = Symbol.VolumeForFixedRisk(MaxRisk, stopLossInPips);
            return Symbol.NormalizeVolumeInUnits(volume, RoundingMode.Down);
        }
        
        private bool ManagePositions()
        {
            var openPositions = Positions.Where(p => p.Label == BotLabel && p.SymbolName == Symbol.Name && _managedTrades.ContainsKey(p.Id)).ToList();
            if (!openPositions.Any()) return false;
            if (openPositions.Count >= MaxPyramidingPositions) return true;

            var threshold = PyramidingThresholdPips * Symbol.PipSize;
            var tradeType = openPositions.First().TradeType;

            bool canPyramid = tradeType == TradeType.Buy ? openPositions.All(p => Symbol.Ask > p.EntryPrice + threshold) : openPositions.All(p => Symbol.Bid < p.EntryPrice - threshold);
            if (!canPyramid) return true;

            Print("Pyramiding condition met. Scaling in.");

            double totalRisk = 0;
            foreach (var pos in openPositions)
            {
                var tradeInfo = _managedTrades[pos.Id];
                var stopLossInPips = Math.Abs(pos.EntryPrice - tradeInfo.StopLossPrice) / Symbol.PipSize;
                totalRisk += Symbol.AmountRisked(pos.VolumeInUnits, stopLossInPips);
            }

            if (totalRisk <= 0)
            {
                Print("Cannot pyramid. Calculated risk is zero.");
                return true;
            }

            var lastPosition = openPositions.OrderBy(p => p.EntryTime).Last();
            var lastTradeInfo = _managedTrades[lastPosition.Id];
            var stopLossPipsForNewPosition = lastTradeInfo.InitialStopLossPips;
            
            var originalPosition = openPositions.OrderBy(p => p.EntryTime).First();
            var originalTradeInfo = _managedTrades[originalPosition.Id];
            
            foreach (var pos in openPositions)
            {
                _managedTrades[pos.Id].StopLossPrice = pos.EntryPrice;
            }
            Print($"{openPositions.Count} existing position(s) moved to custom break-even.");

            var volumeForNewPosition = Symbol.VolumeForFixedRisk(totalRisk, stopLossPipsForNewPosition);
            var normalizedVolume = Symbol.NormalizeVolumeInUnits(volumeForNewPosition, RoundingMode.Down);

            if (normalizedVolume < MinStopLossPips)
            {
                Print($"Calculated pyramid volume ({normalizedVolume}) is below minimum. Aborting scale-in.");
                return true;
            }

            var currentPrice = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
            var newSlPrice = tradeType == TradeType.Buy ? currentPrice - stopLossPipsForNewPosition * Symbol.PipSize : currentPrice + stopLossPipsForNewPosition * Symbol.PipSize;
            
            var newTradeInfo = new TradeInfo
            {
                StopLossPrice = newSlPrice,
                TakeProfitPrice = UseTrailingStop ? (double?)null : originalTradeInfo.TakeProfitPrice,
                InitialStopLossPips = stopLossPipsForNewPosition
            };

            var result = ExecuteMarketOrder(tradeType, Symbol.Name, normalizedVolume, BotLabel, null, null, "Pyramid Position");
            if (result.IsSuccessful)
            {
                _managedTrades[result.Position.Id] = newTradeInfo;
                Print($"Managing new pyramid position {result.Position.Id} with custom SL: {newTradeInfo.StopLossPrice:F5}");
            }
            else
            {
                Print($"Failed to open pyramid position. Error: {result.Error}");
            }

            return true;
        }

        private void VisualizeBias(bool isBullish, bool isBearish)
        {
            Chart.ColorSettings.GridLinesColor = (isBullish || isBearish) ? _originalColor : Chart.ColorSettings.BackgroundColor;
        }

        private void VisualizePeekValues()
        {
            const string highLineName = "peekHighLine", lowLineName = "peekLowLine";
            Chart.RemoveObject(highLineName); Chart.RemoveObject(lowLineName);
            if (isBullishBias)
            {
                if (_hh > 0) Chart.DrawHorizontalLine(highLineName, _hh, Color.Crimson, 1, LineStyle.Dots);
                if (_lowestLows.Count > 0) Chart.DrawHorizontalLine(lowLineName, _lowestLows.Peek(), Color.CornflowerBlue, 1, LineStyle.Dots);
            }
            else if (isBearishBias)
            {
                if (_highestHighs.Count > 0) Chart.DrawHorizontalLine(highLineName, _highestHighs.Peek(), Color.Crimson, 1, LineStyle.Dots);
                if (_ll > 0) Chart.DrawHorizontalLine(lowLineName, _ll, Color.CornflowerBlue, 1, LineStyle.Dots);
            }
        }
    }
}