// File: NT8StrategyBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NinjaTrader.Cbi;
using NinjaTrader.Data;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.Strategies;
using NT8.Adapters.NinjaTrader;
using NT8.Core.Common.Interfaces;
using NT8.Core.Common.Models;
using NT8.Core.Execution;
using NT8.Core.Logging;
using NT8.Core.Risk;
using NT8.Core.Sizing;
using SdkPosition = NT8.Core.Common.Models.Position;
using SdkOrderSide = NT8.Core.Common.Models.OrderSide;
using SdkOrderType = NT8.Core.Common.Models.OrderType;
using OmsOrderRequest = NT8.Core.OMS.OrderRequest;
using OmsOrderSide = NT8.Core.OMS.OrderSide;
using OmsOrderType = NT8.Core.OMS.OrderType;
using OmsOrderState = NT8.Core.OMS.OrderState;
using OmsOrderStatus = NT8.Core.OMS.OrderStatus;

namespace NinjaTrader.NinjaScript.Strategies
{
    /// <summary>
    /// Base class for strategies that integrate NT8 SDK components.
    /// </summary>
    public abstract class NT8StrategyBase : Strategy
    {
        private readonly object _lock = new object();

        protected IStrategy _sdkStrategy;
        protected IRiskManager _riskManager;
        protected IPositionSizer _positionSizer;
        protected NT8ExecutionAdapter _executionAdapter;
        protected ILogger _logger;

        protected StrategyConfig _strategyConfig;
        protected RiskConfig _riskConfig;
        protected SizingConfig _sizingConfig;

        private bool _sdkInitialized;
        private AccountInfo _lastAccountInfo;
        private SdkPosition _lastPosition;
        private MarketSession _currentSession;
        private int _ordersSubmittedToday;
        private DateTime _lastBarTime;
        private bool _killSwitchTriggered;
        private ExecutionCircuitBreaker _circuitBreaker;

        #region User-Configurable Properties

        [NinjaScriptProperty]
        [Display(Name = "Enable SDK", GroupName = "SDK", Order = 1)]
        public bool EnableSDK { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Daily Loss Limit", GroupName = "Risk", Order = 1)]
        public double DailyLossLimit { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Max Trade Risk", GroupName = "Risk", Order = 2)]
        public double MaxTradeRisk { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Max Positions", GroupName = "Risk", Order = 3)]
        public int MaxOpenPositions { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Risk Per Trade", GroupName = "Sizing", Order = 1)]
        public double RiskPerTrade { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Min Contracts", GroupName = "Sizing", Order = 2)]
        public int MinContracts { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Max Contracts", GroupName = "Sizing", Order = 3)]
        public int MaxContracts { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Kill Switch (Flatten + Stop)", GroupName = "Emergency Controls", Order = 1)]
        public bool EnableKillSwitch { get; set; }

        [NinjaScriptProperty]
        [Display(Name = "Verbose Logging", GroupName = "Debug", Order = 1)]
        public bool EnableVerboseLogging { get; set; }

        #endregion

        /// <summary>
        /// Create the SDK strategy instance.
        /// </summary>
        protected abstract IStrategy CreateSdkStrategy();

        /// <summary>
        /// Configure strategy-specific values after initialization.
        /// </summary>
        protected abstract void ConfigureStrategyParameters();

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description = "SDK-integrated strategy base";
                // Name intentionally not set - this is an abstract base class
                Calculate = Calculate.OnBarClose;
                EntriesPerDirection = 1;
                EntryHandling = EntryHandling.AllEntries;
                IsExitOnSessionCloseStrategy = true;
                ExitOnSessionCloseSeconds = 30;
                IsFillLimitOnTouch = false;
                MaximumBarsLookBack = MaximumBarsLookBack.TwoHundredFiftySix;
                OrderFillResolution = OrderFillResolution.Standard;
                Slippage = 0;
                StartBehavior = StartBehavior.WaitUntilFlat;
                TimeInForce = TimeInForce.Gtc;
                TraceOrders = false;
                RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose;
                StopTargetHandling = StopTargetHandling.PerEntryExecution;
                BarsRequiredToTrade = 20;

                EnableSDK = true;
                DailyLossLimit = 1000.0;
                MaxTradeRisk = 200.0;
                MaxOpenPositions = 3;
                RiskPerTrade = 100.0;
                MinContracts = 1;
                MaxContracts = 10;
                EnableKillSwitch = false;
                EnableVerboseLogging = false;
                _killSwitchTriggered = false;
            }
            else if (State == State.DataLoaded)
            {
                if (EnableSDK)
                {
                    try
                    {
                        InitializeSdkComponents();
                        _sdkInitialized = true;
                        Print(string.Format("[SDK] {0} initialized successfully", Name));
                    }
                    catch (Exception ex)
                    {
                        Print(string.Format("[SDK ERROR] Initialization failed: {0}", ex.Message));
                        Log(string.Format("[SDK ERROR] {0}", ex.ToString()), NinjaTrader.Cbi.LogLevel.Error);
                        _sdkInitialized = false;
                    }
                }
            }
        }

        protected override void OnBarUpdate()
        {
            // Kill switch check — must be first
            if (EnableKillSwitch)
            {
                if (!_killSwitchTriggered)
                {
                    _killSwitchTriggered = true;
                    Print(string.Format("[SDK] KILL SWITCH ACTIVATED at {0} — flattening all positions.", Time[0]));
                    try
                    {
                        ExitLong("KillSwitch");
                        ExitShort("KillSwitch");
                    }
                    catch (Exception ex)
                    {
                        Print(string.Format("[SDK] Kill switch flatten error: {0}", ex.Message));
                    }
                }

                return;
            }

            if (!_sdkInitialized || _sdkStrategy == null)
            {
                if (CurrentBar == 0)
                    Print(string.Format("[SDK] Not initialized: sdkInit={0}, strategy={1}", _sdkInitialized, _sdkStrategy != null));
                return;
            }

            if (CurrentBar < BarsRequiredToTrade)
            {
                if (CurrentBar == 0)
                    Print(string.Format("[SDK] Waiting for bars: current={0}, required={1}", CurrentBar, BarsRequiredToTrade));
                return;
            }

            if (Time[0] == _lastBarTime)
                return;

            _lastBarTime = Time[0];

            // Log first processable bar and every 100th bar.
            if (CurrentBar == BarsRequiredToTrade || CurrentBar % 100 == 0)
            {
                Print(string.Format("[SDK] Processing bar {0}: {1} O={2:F2} H={3:F2} L={4:F2} C={5:F2}",
                    CurrentBar,
                    Time[0].ToString("yyyy-MM-dd HH:mm"),
                    Open[0],
                    High[0],
                    Low[0],
                    Close[0]));
            }

            try
            {
                var barData = ConvertCurrentBar();
                var context = BuildStrategyContext();

                StrategyIntent intent;
                lock (_lock)
                {
                    intent = _sdkStrategy.OnBar(barData, context);
                }

                if (intent != null)
                {
                    Print(string.Format("[SDK] Intent generated: {0} {1} @ {2}", intent.Side, intent.Symbol, intent.EntryType));
                    ProcessStrategyIntent(intent, context);
                }
            }
            catch (Exception ex)
            {
                if (_logger != null)
                    _logger.LogError("OnBarUpdate failed: {0}", ex.Message);

                Print(string.Format("[SDK ERROR] OnBarUpdate: {0}", ex.Message));
                Log(string.Format("[SDK ERROR] {0}", ex.ToString()), NinjaTrader.Cbi.LogLevel.Error);
            }
        }

        protected override void OnOrderUpdate(
            Order order,
            double limitPrice,
            double stopPrice,
            int quantity,
            int filled,
            double averageFillPrice,
            NinjaTrader.Cbi.OrderState orderState,
            DateTime time,
            ErrorCode errorCode,
            string nativeError)
        {
            if (!_sdkInitialized || _executionAdapter == null || order == null)
                return;

            if (string.IsNullOrEmpty(order.Name) || !order.Name.StartsWith("SDK_"))
                return;

            // Record NT8 rejections in circuit breaker
            if (orderState == NinjaTrader.Cbi.OrderState.Rejected && _circuitBreaker != null)
            {
                var reason = string.Format("{0} {1}", errorCode, nativeError ?? string.Empty);
                _circuitBreaker.RecordOrderRejection(reason);
                Print(string.Format("[SDK] Order rejected by NT8: {0}", reason));
            }

            _executionAdapter.ProcessOrderUpdate(
                order.OrderId,
                order.Name,
                orderState.ToString(),
                filled,
                averageFillPrice,
                (int)errorCode,
                nativeError);
        }

        protected override void OnExecutionUpdate(
            Execution execution,
            string executionId,
            double price,
            int quantity,
            MarketPosition marketPosition,
            string orderId,
            DateTime time)
        {
            if (!_sdkInitialized || _executionAdapter == null || execution == null || execution.Order == null)
                return;

            if (string.IsNullOrEmpty(execution.Order.Name) || !execution.Order.Name.StartsWith("SDK_"))
                return;

            _executionAdapter.ProcessExecution(orderId, executionId, price, quantity, time);
        }

        private void InitializeSdkComponents()
        {
            _logger = new BasicLogger(Name);

            Print(string.Format("[SDK] Initializing with: DailyLoss={0:C}, TradeRisk={1:C}, MaxPos={2}",
                DailyLossLimit,
                MaxTradeRisk,
                MaxOpenPositions));

            _riskConfig = new RiskConfig(DailyLossLimit, MaxTradeRisk, MaxOpenPositions, true);
            _sizingConfig = new SizingConfig(
                SizingMethod.FixedDollarRisk,
                MinContracts,
                MaxContracts,
                RiskPerTrade,
                new Dictionary<string, object>());

            _strategyConfig = new StrategyConfig(
                Name,
                Instrument.MasterInstrument.Name,
                new Dictionary<string, object>(),
                _riskConfig,
                _sizingConfig);

            _riskManager = new BasicRiskManager(_logger);
            _positionSizer = new BasicPositionSizer(_logger);
            _circuitBreaker = new ExecutionCircuitBreaker(
                _logger,
                failureThreshold: 3,
                timeout: TimeSpan.FromSeconds(30));
            _executionAdapter = new NT8ExecutionAdapter();

            _sdkStrategy = CreateSdkStrategy();
            if (_sdkStrategy == null)
                throw new InvalidOperationException("CreateSdkStrategy returned null");

            _sdkStrategy.Initialize(_strategyConfig, null, _logger);
            ConfigureStrategyParameters();

            _ordersSubmittedToday = 0;
            _lastBarTime = DateTime.MinValue;
            _lastAccountInfo = null;
            _lastPosition = null;
            _currentSession = null;
        }

        private BarData ConvertCurrentBar()
        {
            return NT8DataConverter.ConvertBar(
                Instrument.MasterInstrument.Name,
                Time[0],
                Open[0],
                High[0],
                Low[0],
                Close[0],
                (long)Volume[0],
                (int)BarsPeriod.Value);
        }

        private StrategyContext BuildStrategyContext()
        {
            var customData = new Dictionary<string, object>();
            customData.Add("CurrentBar", CurrentBar);
            customData.Add("BarsRequiredToTrade", BarsRequiredToTrade);
            customData.Add("OrdersToday", _ordersSubmittedToday);

            return NT8DataConverter.ConvertContext(
                Instrument.MasterInstrument.Name,
                Time[0],
                BuildPositionInfo(),
                BuildAccountInfo(),
                BuildSessionInfo(),
                customData);
        }

        private AccountInfo BuildAccountInfo()
        {
            var accountInfo = NT8DataConverter.ConvertAccount(100000.0, 250000.0, 0.0, 0.0, DateTime.UtcNow);
            _lastAccountInfo = accountInfo;
            return accountInfo;
        }

        private SdkPosition BuildPositionInfo()
        {
            var p = NT8DataConverter.ConvertPosition(
                Instrument.MasterInstrument.Name,
                Position.Quantity,
                Position.AveragePrice,
                0.0,
                0.0,
                DateTime.UtcNow);

            _lastPosition = p;
            return p;
        }

        private MarketSession BuildSessionInfo()
        {
            if (_currentSession != null && _currentSession.SessionStart.Date == Time[0].Date)
                return _currentSession;

            var sessionStart = Time[0].Date.AddHours(9).AddMinutes(30);
            var sessionEnd = Time[0].Date.AddHours(16);
            var isRth = Time[0].Hour >= 9 && Time[0].Hour < 16;
            var sessionName = isRth ? "RTH" : "ETH";

            _currentSession = NT8DataConverter.ConvertSession(sessionStart, sessionEnd, isRth, sessionName);
            return _currentSession;
        }

        private void ProcessStrategyIntent(StrategyIntent intent, StrategyContext context)
        {
            if (EnableVerboseLogging)
                Print(string.Format("[SDK] Validating intent: {0} {1}", intent.Side, intent.Symbol));

            var riskDecision = _riskManager.ValidateOrder(intent, context, _riskConfig);
            if (!riskDecision.Allow)
            {
                if (EnableVerboseLogging)
                    Print(string.Format("[SDK] Risk REJECTED: {0}", riskDecision.RejectReason));
                if (_logger != null)
                    _logger.LogWarning("Intent rejected by risk manager: {0}", riskDecision.RejectReason);
                return;
            }

            if (EnableVerboseLogging)
                Print(string.Format("[SDK] Risk approved"));

            var sizingResult = _positionSizer.CalculateSize(intent, context, _sizingConfig);
            if (EnableVerboseLogging)
            {
                Print(string.Format("[SDK] Position size: {0} contracts (min={1}, max={2})",
                    sizingResult.Contracts,
                    MinContracts,
                    MaxContracts));
            }

            if (sizingResult.Contracts < MinContracts)
            {
                if (EnableVerboseLogging)
                    Print(string.Format("[SDK] Size too small: {0} < {1}", sizingResult.Contracts, MinContracts));
                return;
            }

            var request = new OmsOrderRequest();
            request.Symbol = intent.Symbol;
            request.Side = MapOrderSide(intent.Side);
            request.Type = MapOrderType(intent.EntryType);
            request.Quantity = sizingResult.Contracts;
            request.LimitPrice = intent.LimitPrice.HasValue ? (decimal?)intent.LimitPrice.Value : null;
            request.StopPrice = null;

            if (EnableVerboseLogging)
            {
                Print(string.Format("[SDK] Submitting order: {0} {1} {2} @ {3}",
                    request.Side,
                    request.Quantity,
                    request.Symbol,
                    request.Type));
            }

            SubmitOrderToNT8(request, intent);
            _ordersSubmittedToday++;
        }

        private void SubmitOrderToNT8(OmsOrderRequest request, StrategyIntent intent)
        {
            // Circuit breaker gate
            if (_circuitBreaker != null && !_circuitBreaker.ShouldAllowOrder())
            {
                var state = _circuitBreaker.GetState();
                Print(string.Format("[SDK] Circuit breaker OPEN — order blocked: {0}", state.Reason));
                if (_logger != null)
                    _logger.LogWarning("Circuit breaker blocked order: {0}", state.Reason);
                return;
            }

            try
            {
                var orderName = string.Format("SDK_{0}_{1}", intent.Symbol, DateTime.Now.Ticks);
                _executionAdapter.SubmitOrder(request, orderName);

                if (request.Side == OmsOrderSide.Buy)
                {
                    if (request.Type == OmsOrderType.Market)
                        EnterLong(request.Quantity, orderName);
                    else if (request.Type == OmsOrderType.Limit && request.LimitPrice.HasValue)
                        EnterLongLimit(request.Quantity, (double)request.LimitPrice.Value, orderName);
                    else if (request.Type == OmsOrderType.StopMarket && request.StopPrice.HasValue)
                        EnterLongStopMarket(request.Quantity, (double)request.StopPrice.Value, orderName);
                }
                else if (request.Side == OmsOrderSide.Sell)
                {
                    if (request.Type == OmsOrderType.Market)
                        EnterShort(request.Quantity, orderName);
                    else if (request.Type == OmsOrderType.Limit && request.LimitPrice.HasValue)
                        EnterShortLimit(request.Quantity, (double)request.LimitPrice.Value, orderName);
                    else if (request.Type == OmsOrderType.StopMarket && request.StopPrice.HasValue)
                        EnterShortStopMarket(request.Quantity, (double)request.StopPrice.Value, orderName);
                }

                if (intent.StopTicks > 0)
                    SetStopLoss(orderName, CalculationMode.Ticks, (int)intent.StopTicks, false);

                if (intent.TargetTicks.HasValue && intent.TargetTicks.Value > 0)
                    SetProfitTarget(orderName, CalculationMode.Ticks, (int)intent.TargetTicks.Value);

                if (_circuitBreaker != null)
                    _circuitBreaker.OnSuccess();
            }
            catch (Exception ex)
            {
                if (_circuitBreaker != null)
                    _circuitBreaker.OnFailure();

                Print(string.Format("[SDK] SubmitOrderToNT8 failed: {0}", ex.Message));
                if (_logger != null)
                    _logger.LogError("SubmitOrderToNT8 failed: {0}", ex.Message);
                throw;
            }
        }

        private static OmsOrderSide MapOrderSide(SdkOrderSide side)
        {
            if (side == SdkOrderSide.Buy)
                return OmsOrderSide.Buy;
            return OmsOrderSide.Sell;
        }

        private static OmsOrderType MapOrderType(SdkOrderType type)
        {
            if (type == SdkOrderType.Market)
                return OmsOrderType.Market;
            if (type == SdkOrderType.Limit)
                return OmsOrderType.Limit;
            if (type == SdkOrderType.StopLimit)
                return OmsOrderType.StopLimit;
            return OmsOrderType.StopMarket;
        }

        protected OmsOrderStatus GetSdkOrderStatus(string orderName)
        {
            if (_executionAdapter == null)
                return null;
            return _executionAdapter.GetOrderStatus(orderName);
        }
    }
}

