using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System;
using MS; // Required for LowPassFilter and MMI from your provided file

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class BesselFilterAtrSLBot : Robot
    {
        [Parameter("Cutoff Period", DefaultValue = 20, MinValue = 3)] // Renamed from FilterPeriodParameter
        public int CutoffPeriodParameter { get; set; }

        [Parameter("ATR Period", DefaultValue = 14, MinValue = 1)]
        public int AtrPeriodParameter { get; set; }

        [Parameter("MMI Period", DefaultValue = 20, MinValue = 2)]
        public int MmiPeriodParameter { get; set; }

        [Parameter("Take Profit Factor (x SL)", DefaultValue = 2.0, MinValue = 0.1, Step = 0.1)]
        public double TakeProfitFactor { get; set; }

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

        [Parameter("Bot Label", DefaultValue = "BesselMMIBotV12")] // V12
        public string BotLabel { get; set; }

        private LowPassFilter besselFilter;
        private IndicatorDataSeries filterOutput; 
        private AverageTrueRange atr;
        
        private MS.MMI mmiCalculator; 
        private IndicatorDataSeries mmiOutput;   

        private const double SamplingFrequency = 1.0; // Sampling frequency is 1 sample per bar

        protected override void OnStart()
        {
            // Calculate cutoffFrequency based on the new CutoffPeriodParameter
            // samplingFrequency is fixed at 1.0
            // cutoffFrequency = 1.0 / CutoffPeriodParameter
            double actualCutoffFrequency = 1.0 / CutoffPeriodParameter;

            // Validate the calculated cutoff frequency.
            // It must be positive and strictly less than Nyquist frequency (SamplingFrequency / 2.0, which is 0.5).
            // For CutoffPeriodParameter >= 3:
            // 1.0 / CutoffPeriodParameter > 0 is true.
            // 1.0 / CutoffPeriodParameter < 0.5 implies CutoffPeriodParameter > 2, which is true.
            // The check is kept for robustness.
            if (actualCutoffFrequency <= 0 || actualCutoffFrequency >= SamplingFrequency / 2.0)
            {
                Print($"Error: Invalid CutoffPeriodParameter ({CutoffPeriodParameter}). " +
                      $"The resulting Cutoff Frequency ({actualCutoffFrequency:F4}) must be > 0 and < {SamplingFrequency / 2.0}. " +
                      $"This implies CutoffPeriodParameter must be > 2.");
                Stop();
                return;
            }

            besselFilter = LowPassFilter.CreateBesselLowPass(actualCutoffFrequency, SamplingFrequency);
            filterOutput = CreateDataSeries(); 
            atr = Indicators.AverageTrueRange(AtrPeriodParameter, MovingAverageType.Simple);

            mmiCalculator = new MS.MMI(MmiPeriodParameter);
            mmiOutput = CreateDataSeries();

            besselFilter.Reset(); 
            mmiCalculator.Reset(); 

            for (int i = 0; i < Bars.Count; i++) 
            {
                double historicalClose = Bars.ClosePrices[i];
                
                double filterVal = besselFilter.Update(historicalClose); 
                filterOutput[i] = filterVal; 

                double mmiVal = mmiCalculator.Update(historicalClose); 
                mmiOutput[i] = mmiVal;
            }
        }

        protected override void OnBar()
        {
            if (Bars.Count < 4) 
            {
                Print($"Bars.Count = {Bars.Count}. Waiting for more history (need Bars.Count >= 4 for Last(3) access).");
                return;
            }

            // Check if enough bars have loaded for ATR calculation.
            // AtrPeriodParameter is the period for ATR, not related to CutoffPeriodParameter directly for this check.
            if (Bars.Count <= AtrPeriodParameter)
            {
                Print($"Bars.Count = {Bars.Count}. Waiting for more bars for ATR calculation (AtrPeriod: {AtrPeriodParameter}).");
                return;
            }

            double currentAtrValue = atr.Result.Last(1); 
            if (double.IsNaN(currentAtrValue) || currentAtrValue <= 0)
            {
                Print($"ATR value is not valid (NaN or <=0): {currentAtrValue:F5} for bar index {Bars.Count - 2}. Trade checks skipped.");
                return;
            }

            double closeOfRecentlyClosedBar = Bars.ClosePrices.Last(1); 
            
            double currentFilterValue = besselFilter.Update(closeOfRecentlyClosedBar);
            filterOutput[Bars.Count - 2] = currentFilterValue;

            double currentMmiValue = mmiCalculator.Update(closeOfRecentlyClosedBar);
            mmiOutput[Bars.Count - 2] = currentMmiValue;
            
            double filterVal_L1_Recent = filterOutput.Last(1);  
            double filterVal_L2_TurnPoint = filterOutput.Last(2); 
            double filterVal_L3_Prior = filterOutput.Last(3);  

            if (double.IsNaN(filterVal_L1_Recent) || double.IsNaN(filterVal_L2_TurnPoint) || double.IsNaN(filterVal_L3_Prior))
            {
                Print($"Filter values contain NaN. Waiting for filter to stabilize. L1: {filterVal_L1_Recent}, L2: {filterVal_L2_TurnPoint}, L3: {filterVal_L3_Prior}");
                return;
            }
            
            bool isLowPoint = filterVal_L3_Prior > filterVal_L2_TurnPoint && filterVal_L2_TurnPoint < filterVal_L1_Recent;
            bool isHighPoint = filterVal_L3_Prior < filterVal_L2_TurnPoint && filterVal_L2_TurnPoint > filterVal_L1_Recent;

            if (isLowPoint || isHighPoint)
            {
                double mmi_L1_Recent = mmiOutput.Last(1);
                double mmi_L2_Prior = mmiOutput.Last(2);

                if (double.IsNaN(mmi_L1_Recent) || double.IsNaN(mmi_L2_Prior))
                {
                    Print($"MMI values for comparison contain NaN. MMI_L1_Recent: {mmi_L1_Recent}, MMI_L2_Prior: {mmi_L2_Prior}. Trade condition not met.");
                    return;
                }

                bool isMmiFalling = mmi_L2_Prior > mmi_L1_Recent;

                if (isMmiFalling)
                {
                    Print($"MMI is falling (Prior L2: {mmi_L2_Prior:F2} > Recent L1: {mmi_L1_Recent:F2}). Bessel signal confirmed.");
                    if (isLowPoint)
                    {
                        ClosePositions(TradeType.Sell);
                        if (Positions.FindAll(BotLabel, SymbolName, TradeType.Buy).Length == 0)
                        {
                            ExecuteOrder(TradeType.Buy, "Long Entry (Bessel Low, MMI Falling)", currentAtrValue);
                        }
                    }
                    else // isHighPoint
                    {
                        ClosePositions(TradeType.Buy);
                        if (Positions.FindAll(BotLabel, SymbolName, TradeType.Sell).Length == 0)
                        {
                            ExecuteOrder(TradeType.Sell, "Short Entry (Bessel High, MMI Falling)", currentAtrValue);
                        }
                    }
                }
                else
                {
                    Print($"MMI is not falling (Prior L2: {mmi_L2_Prior:F2}, Recent L1: {mmi_L1_Recent:F2}). Trade condition not met.");
                }
            }
        }

        private void ExecuteOrder(TradeType tradeType, string comment, double validatedAtrValue)
        {
            double volume = Symbol.QuantityToVolumeInUnits(VolumeInLots);
            double stopLossDistanceInPrice = 4 * validatedAtrValue;
            double stopLossInPips = stopLossDistanceInPrice / Symbol.PipSize;

            if (stopLossInPips < 1.0) 
            {
                 Print($"Calculated Stop Loss in pips ({stopLossInPips:F2}) is less than 1. Adjusting to 1 pip for order '{comment}'. ATR ({validatedAtrValue:F5}), PipSize ({Symbol.PipSize:F5}).");
                 stopLossInPips = 1.0;
            }
            
            double? takeProfitInPips = null; 
            if (TakeProfitFactor > 0)
            {
                takeProfitInPips = stopLossInPips * TakeProfitFactor;
                if (takeProfitInPips < 1.0) 
                {
                    takeProfitInPips = 1.0;
                    Print($"Calculated Take Profit in pips ({stopLossInPips * TakeProfitFactor:F2}) is less than 1. Adjusting to 1 pip.");
                }
            }

            ExecuteMarketOrder(tradeType, SymbolName, volume, BotLabel, stopLossInPips, takeProfitInPips, comment);
        }

        private void ClosePositions(TradeType tradeType)
        {
            foreach (var position in Positions.FindAll(BotLabel, SymbolName, tradeType))
            {
                ClosePosition(position);
            }
        }

        protected override void OnStop()
        {
        }
    }
}