using System;
using System.Linq; // Added for .Distinct() and .Any()
using System.Collections.Generic; // Added for List<T>
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    // Defines the trading direction
    public enum TradeDirection
    {
        Both,
        Long,
        Short
    }

    [Robot(AccessRights = AccessRights.None)]
    public class HmaTrendTrader : Robot
    {
        [Parameter("Trade Direction", DefaultValue = TradeDirection.Both)]
        public TradeDirection TradeDir { get; set; }

        [Parameter("HMA Period", DefaultValue = 25, MinValue = 2)]
        public int HmaPeriod { get; set; }

        [Parameter("Max Positions (per Direction)", DefaultValue = 3, MinValue = 1)]
        public int MaxPositions { get; set; }

        [Parameter("Risk (Pips)", DefaultValue = 125, MinValue = 1)]
        public double RiskPips { get; set; }

        [Parameter("Volume (Lots)", DefaultValue = 0.1, MinValue = 0.01, Step = 0.01)]
        public double VolumeLots { get; set; }

        [Parameter("Enable Grid Take Profit", DefaultValue = true)]
        public bool EnableGridTakeProfit { get; set; }
        
        [Parameter("Enable Opposite Exit", DefaultValue = true)]
        public bool EnableOppositeExit { get; set; }
        
        [Parameter("Enable Volume Scaling", DefaultValue = false)]
        public bool EnableVolumeScaling { get; set; }

        [Parameter("Cooldown Bars", DefaultValue = 1, MinValue = 0)]
        public int CooldownBars { get; set; }

        private HullMovingAverage _hma;
        private double _volumeInUnits;

        // Bias state updated by OnBarClosed
        private bool _isShortBias = false;
        private bool _isLongBias = false;

        // Cooldown tracking
        private int _lastLongExitBarIndex = -1;
        private int _lastShortExitBarIndex = -1;
        
        // Extremum Tracking stores HMA values
        private double? _lastPeakPrice = null;   
        private double? _lastTroughPrice = null; 
        
        // NEW: Index tracking for comparison
        private int _lastPeakIndex = -1;
        private int _lastTroughIndex = -1;

        protected override void OnStart()
        {
            _hma = Indicators.HullMovingAverage(Bars.ClosePrices, HmaPeriod);
            
            _volumeInUnits = Symbol.QuantityToVolumeInUnits(VolumeLots);
            _volumeInUnits = Symbol.NormalizeVolumeInUnits(_volumeInUnits, RoundingMode.ToNearest);

            Positions.Closed += OnPositionsClosed;

            Print("HmaTrendTrader started. Managing existing positions:");
            
            var positionsToLog = new List<Position>();
            
            if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both)
            {
                // Get all existing Long positions for this symbol
                positionsToLog.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Buy));
            }
            if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both)
            {
                // Get all existing Short positions for this symbol
                positionsToLog.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Sell));
            }

            // Use Distinct() in case TradeDir == Both
            foreach (var pos in positionsToLog.Distinct())
            {
                Print($"   -> Takeover: {pos.TradeType} ID {pos.Id}, Vol: {pos.VolumeInUnits}, Entry: {pos.EntryPrice}, SL: {pos.StopLoss}");
            }

            if (!positionsToLog.Any())
            {
                Print("   -> No existing positions found for management.");
            }
            
            // Added: Initial status report after takeover
            LogStatusReport();
        }

        // Added: Event handler for position closures (Cooldown logic)
        private void OnPositionsClosed(PositionClosedEventArgs args)
        {
            var position = args.Position;

            // Only track positions managed by this bot
            if (position.SymbolName == Symbol.Name)
            {
                // Store the index of the bar *during which* the exit occurred
                int exitBarIndex = Bars.Count - 1;

                if (position.TradeType == TradeType.Buy)
                {
                    _lastLongExitBarIndex = exitBarIndex;
                }
                else if (position.TradeType == TradeType.Sell)
                {
                    _lastShortExitBarIndex = exitBarIndex;
                }
            }
        }

        protected override void OnTick()
        {
            HandleEntry();
            HandleExit();
            HandleExtremumCleanup(); // Cleanup logic runs continuously
        }

        private void HandleExtremumCleanup()
        {
            // --- CLEANUP SHORT EXTREMUM (TROUGH) ---
            // Trigger: Price hits Trough level
            if (_lastTroughPrice.HasValue && Symbol.Bid <= _lastTroughPrice.Value)
            {
                // Reset Condition: Only reset if the Peak is newer (structural validation)
                bool isPeakNewer = _lastPeakPrice.HasValue && (_lastPeakIndex > _lastTroughIndex);

                if (isPeakNewer)
                {
                    Print($"Trough cleanup hit at {_lastTroughPrice}. Resetting Trough (Peak is newer).");
                    _lastTroughPrice = null;
                    _lastTroughIndex = -1;
                }
                // Optional: We could implement aggressive cleanup here if no Peak exists, 
                // but sticking strictly to the user's structural rule.
            }
            
            // --- CLEANUP LONG EXTREMUM (PEAK) ---
            // Trigger: Price hits Peak level
            if (_lastPeakPrice.HasValue && Symbol.Ask >= _lastPeakPrice.Value)
            {
                // Reset Condition: Only reset if the Trough is newer (structural validation)
                bool isTroughNewer = _lastTroughPrice.HasValue && (_lastTroughIndex > _lastPeakIndex);

                if (isTroughNewer)
                {
                    Print($"Peak cleanup hit at {_lastPeakPrice}. Resetting Peak (Trough is newer).");
                    _lastPeakPrice = null;
                    _lastPeakIndex = -1;
                }
            }
        }

        protected override void OnBarClosed()
        {
            bool wasLongBias = _isLongBias;
            bool wasShortBias = _isShortBias;

            // Data for HMA
            var hma_T1 = _hma.Result.Last(0); 
            var hma_T2 = _hma.Result.Last(1); 

            _isShortBias = hma_T1 < hma_T2; 
            _isLongBias = hma_T1 > hma_T2; 
            
            // --- TRACKING EXTREMA (HMA Value at Pivot) ---
            
            // Index of the bar that just closed (used as the creation index for the extremum)
            int currentBarIndex = Bars.Count - 1;

            // 1. Flip from Long to Short (Peak confirmed)
            if (wasLongBias && !_isLongBias)
            {
                _lastPeakPrice = hma_T2;
                _lastPeakIndex = currentBarIndex;
            }

            // 2. Flip from Short to Long (Trough confirmed)
            if (wasShortBias && !_isShortBias)
            {
                _lastTroughPrice = hma_T2;
                _lastTroughIndex = currentBarIndex;
            }

            // --- EXIT LOGIC (Grid) ---
            
            if (EnableGridTakeProfit)
            {
                if (wasLongBias && !_isLongBias)
                {
                    if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both)
                    {
                        var shorts = GetManagedPositions().Where(p => p.TradeType == TradeType.Sell).ToList();
                        
                        if (shorts.Count > 0 && shorts.Sum(p => p.GrossProfit) > 0)
                        {
                            Print($"HMA Peak detected. Closing {shorts.Count} Short positions (Total PnL > 0).");
                            foreach (var pos in shorts) ClosePosition(pos);
                        }
                    }
                }
                
                if (wasShortBias && !_isShortBias)
                {
                    if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both)
                    {
                        var longs = GetManagedPositions().Where(p => p.TradeType == TradeType.Buy).ToList();
                        
                        if (longs.Count > 0 && longs.Sum(p => p.GrossProfit) > 0)
                        {
                            Print($"HMA Trough detected. Closing {longs.Count} Long positions (Total PnL > 0).");
                            foreach (var pos in longs) ClosePosition(pos);
                        }
                    }
                }
            }

            HandleBreakevenMove();
            LogStatusReport();
        }

        private void HandleEntry()
        {
            if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) HandleLongEntry();
            if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) HandleShortEntry();
        }

        private void HandleShortEntry()
        {
            // Cooldown check
            if (_lastShortExitBarIndex > -1 && (Bars.Count - 1 < _lastShortExitBarIndex + CooldownBars)) return;

            // Conditions
            if (!_isShortBias) return;
            if (Bars.HighPrices.Last(1) > Bars.HighPrices.Last(2)) return;
            if (Symbol.Bid <= Bars.HighPrices.Last(1)) return;
            if (GetAtRiskPositionsCount(TradeType.Sell) > 0) return;
            
            int positionCount = GetManagedPositionsCount(TradeType.Sell);
            if (positionCount >= MaxPositions) return;

            // Volume
            double volumeToExecute = _volumeInUnits; 
            if (EnableVolumeScaling) volumeToExecute = _volumeInUnits * (positionCount + 1);
            volumeToExecute = NormalizeVolume(volumeToExecute);
            if (volumeToExecute == 0) return;

            var result = ExecuteMarketOrder(TradeType.Sell, Symbol.Name, volumeToExecute, null, RiskPips, null);

            if (result.IsSuccessful)
            {
                // TP LOGIC: Set TP for the new position to the last Trough price
                if (_lastTroughPrice.HasValue)
                {
                    TrySetTakeProfit(
                        result.Position, 
                        _lastTroughPrice.Value, 
                        $"TP set for new Short {result.Position.Id} at Trough price: {_lastTroughPrice}");
                }
                else
                {
                    Print($"INFO: Short Entry {result.Position.Id} was placed, but _lastTroughPrice is NULL. TP not set.");
                }
                
                if (EnableOppositeExit) ManageOppositePositions(TradeType.Sell);
            }
        }

        private void HandleLongEntry()
        {
            if (_lastLongExitBarIndex > -1 && (Bars.Count - 1 < _lastLongExitBarIndex + CooldownBars)) return;
            if (!_isLongBias) return;
            if (Bars.LowPrices.Last(1) < Bars.LowPrices.Last(2)) return;
            if (Symbol.Ask >= Bars.LowPrices.Last(1)) return;
            if (GetAtRiskPositionsCount(TradeType.Buy) > 0) return;

            int positionCount = GetManagedPositionsCount(TradeType.Buy);
            if (positionCount >= MaxPositions) return;

            double volumeToExecute = _volumeInUnits; 
            if (EnableVolumeScaling) volumeToExecute = _volumeInUnits * (positionCount + 1);

            volumeToExecute = NormalizeVolume(volumeToExecute);
            if (volumeToExecute == 0) return;

            var result = ExecuteMarketOrder(TradeType.Buy, Symbol.Name, volumeToExecute, null, RiskPips, null);

            if (result.IsSuccessful)
            {
                // TP LOGIC: Set TP for the new position to the last Peak price
                if (_lastPeakPrice.HasValue)
                {
                    TrySetTakeProfit(
                        result.Position, 
                        _lastPeakPrice.Value, 
                        $"TP set for new Long {result.Position.Id} at Peak price: {_lastPeakPrice}");
                }
                else
                {
                    Print($"INFO: Long Entry {result.Position.Id} was placed, but _lastPeakPrice is NULL. TP not set.");
                }
                
                if (EnableOppositeExit) ManageOppositePositions(TradeType.Buy);
            }
        }

        private void TrySetTakeProfit(Position position, double tpPrice, string logMessage)
        {
            tpPrice = Math.Round(tpPrice, Symbol.Digits);
            
            try
            {
                ModifyPosition(position, position.StopLoss, tpPrice, ProtectionType.Absolute);
                Print(logMessage);
            }
            catch (Exception ex)
            {
                Print($"ERROR: Failed to set TP for {position.Id} to {tpPrice}. Reason: {ex.Message}");
            }
        }
        
        private void ManageOppositePositions(TradeType newEntryDirection)
        {
            TradeType oppositeType = (newEntryDirection == TradeType.Buy) ? TradeType.Sell : TradeType.Buy;
            var oppositePositions = GetManagedPositions().Where(p => p.TradeType == oppositeType).ToList();

            if (!oppositePositions.Any()) return;

            if (oppositePositions.Sum(p => p.GrossProfit) > 0)
            {
                 Print($"Fresh Entry ({newEntryDirection}) -> Basket Close {oppositeType}.");
                 foreach (var pos in oppositePositions) ClosePosition(pos);
                 return;
            }

            foreach (var pos in oppositePositions)
            {
                if (pos.GrossProfit > 0) ClosePosition(pos);
                else SetScratchTpOrClose(pos);
            }
        }

        private void HandleBreakevenMove()
        {
            var lastClosedHigh = Bars.HighPrices.Last(0);
            var lastClosedLow = Bars.LowPrices.Last(0);
            var buffer = 2 * Symbol.Spread;

            foreach (var pos in GetManagedPositions())
            {
                if (pos.TradeType == TradeType.Sell && (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both))
                {
                    if (pos.StopLoss.HasValue == false || pos.StopLoss > pos.EntryPrice)
                    {
                        if (pos.EntryPrice > (lastClosedHigh + buffer))
                        {
                            var newSL = pos.EntryPrice - Symbol.Spread;
                            if (!pos.StopLoss.HasValue || Math.Abs(pos.StopLoss.Value - newSL) > Symbol.PipSize * 0.1)
                                ModifyPosition(pos, newSL, pos.TakeProfit, ProtectionType.Absolute);
                        }
                    }
                }
                else if (pos.TradeType == TradeType.Buy && (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both))
                {
                    if (pos.StopLoss.HasValue == false || pos.StopLoss < pos.EntryPrice)
                    {
                        if (pos.EntryPrice < (lastClosedLow - buffer))
                        {
                            var newSL = pos.EntryPrice + Symbol.Spread;
                            if (!pos.StopLoss.HasValue || Math.Abs(pos.StopLoss.Value - newSL) > Symbol.PipSize * 0.1)
                                ModifyPosition(pos, newSL, pos.TakeProfit, ProtectionType.Absolute);
                        }
                    }
                }
            }
        }

        private void HandleExit()
        {
            if (_isLongBias) 
            {
                if (TradeDir == TradeDirection.Short || TradeDir == TradeDirection.Both) HandleLossPreventionExit(TradeType.Sell); 
            }
            if (_isShortBias)
            {
                if (TradeDir == TradeDirection.Long || TradeDir == TradeDirection.Both) HandleLossPreventionExit(TradeType.Buy); 
            }
        }

        private void HandleLossPreventionExit(TradeType tradeType)
        {
            foreach (var pos in GetManagedPositions().Where(p => p.TradeType == tradeType && p.GrossProfit <= 0))
            {
                SetScratchTpOrClose(pos);
            }
        }

        private void SetScratchTpOrClose(Position pos)
        {
            double scratchTp;
            double currentPrice;
            bool isBuy = pos.TradeType == TradeType.Buy;

            if (isBuy)
            {
                scratchTp = pos.EntryPrice + Symbol.Spread;
                currentPrice = Symbol.Bid;
            }
            else
            {
                scratchTp = pos.EntryPrice - Symbol.Spread;
                currentPrice = Symbol.Ask;
            }
            
            scratchTp = Math.Round(scratchTp, Symbol.Digits);

            if ((isBuy && currentPrice >= scratchTp) || (!isBuy && currentPrice <= scratchTp))
            {
                ClosePosition(pos);
                return;
            }

            if (pos.TakeProfit.HasValue && Math.Abs(pos.TakeProfit.Value - scratchTp) < Symbol.PipSize) return; 

            try 
            {
                ModifyPosition(pos, pos.StopLoss, scratchTp, ProtectionType.Absolute);
            }
            catch (Exception) {}
        }
        
        private double NormalizeVolume(double vol)
        {
            vol = Symbol.NormalizeVolumeInUnits(vol, RoundingMode.ToNearest);
            if (vol < Symbol.VolumeInUnitsMin) vol = Symbol.VolumeInUnitsMin;
            if (vol > Symbol.VolumeInUnitsMax) vol = Symbol.VolumeInUnitsMax;
            return vol;
        }

        private void LogStatusReport()
        {
            int longPositions = 0, shortPositions = 0, longAtRisk = 0, shortAtRisk = 0;

            if (TradeDir != TradeDirection.Short)
            {
                longPositions = GetManagedPositionsCount(TradeType.Buy);
                longAtRisk = GetAtRiskPositionsCount(TradeType.Buy);
            }
            if (TradeDir != TradeDirection.Long)
            {
                shortPositions = GetManagedPositionsCount(TradeType.Sell);
                shortAtRisk = GetAtRiskPositionsCount(TradeType.Sell);
            }

            var activePositions = GetActiveManagedPositions();
            double openPips = activePositions.Sum(p => p.Pips);
            double todayPips = History.Where(t => t.ClosingTime.Date == Server.Time.Date && t.SymbolName == Symbol.Name).Sum(t => t.Pips);

            string swingInfo = "";
            if (_lastPeakPrice.HasValue) swingInfo += $"PeakHMA: {_lastPeakPrice} ";
            if (_lastTroughPrice.HasValue) swingInfo += $"TroughHMA: {_lastTroughPrice}";

            Print($"Status: Open: {openPips:F1} Pips | Day: {todayPips:F1} Pips | L:{longPositions}({longAtRisk}) S:{shortPositions}({shortAtRisk}) | {swingInfo}");
        }

        private System.Collections.Generic.IEnumerable<Position> GetManagedPositions()
        {
            return Positions.Where(p => p.SymbolName == Symbol.Name);
        }

        private List<Position> GetActiveManagedPositions()
        {
            var managedPositions = new List<Position>();
            if (TradeDir != TradeDirection.Short) managedPositions.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Buy));
            if (TradeDir != TradeDirection.Long) managedPositions.AddRange(GetManagedPositions().Where(p => p.TradeType == TradeType.Sell));
            return managedPositions.Distinct().ToList();
        }

        private int GetManagedPositionsCount(TradeType tradeType) => GetManagedPositions().Count(p => p.TradeType == tradeType);

        private int GetAtRiskPositionsCount(TradeType tradeType)
        {
            if (tradeType == TradeType.Sell)
                return GetManagedPositions().Count(p => p.TradeType == TradeType.Sell && p.StopLoss.HasValue && p.StopLoss > p.EntryPrice);
            else
                return GetManagedPositions().Count(p => p.TradeType == TradeType.Buy && p.StopLoss.HasValue && p.StopLoss < p.EntryPrice);
        }

        protected override void OnStop()
        {
            if (Positions != null)
            {
                Positions.Closed -= OnPositionsClosed;
            }
        }
    }
}