using System;
using System.Collections.Generic;
using NT8.Core.OMS;

namespace NT8.Adapters.NinjaTrader
{
    /// <summary>
    /// Adapter for executing orders through NinjaTrader 8 platform.
    /// Bridges SDK order requests to NT8 order submission and handles callbacks.
    /// Thread-safe for concurrent NT8 callbacks.
    /// </summary>
    public class NT8ExecutionAdapter
    {
        private readonly object _lock = new object();
        private readonly Dictionary<string, OrderTrackingInfo> _orderTracking;
        private readonly Dictionary<string, string> _nt8ToSdkOrderMap;

        /// <summary>
        /// Creates a new NT8 execution adapter.
        /// </summary>
        public NT8ExecutionAdapter()
        {
            _orderTracking = new Dictionary<string, OrderTrackingInfo>();
            _nt8ToSdkOrderMap = new Dictionary<string, string>();
        }

        /// <summary>
        /// Submit an order to NinjaTrader 8.
        /// NOTE: This method tracks order state only. Actual NT8 submission is performed by strategy wrapper code.
        /// </summary>
        /// <param name="request">SDK order request.</param>
        /// <param name="sdkOrderId">Unique SDK order ID.</param>
        /// <returns>Tracking info for the submitted order.</returns>
        /// <exception cref="ArgumentNullException">Thrown when request or sdkOrderId is invalid.</exception>
        /// <exception cref="InvalidOperationException">Thrown when the same order ID is submitted twice.</exception>
        public OrderTrackingInfo SubmitOrder(OrderRequest request, string sdkOrderId)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (string.IsNullOrWhiteSpace(sdkOrderId))
            {
                throw new ArgumentNullException("sdkOrderId");
            }

            try
            {
                lock (_lock)
                {
                    if (_orderTracking.ContainsKey(sdkOrderId))
                    {
                        throw new InvalidOperationException(string.Format("Order {0} already exists", sdkOrderId));
                    }

                    var trackingInfo = new OrderTrackingInfo();
                    trackingInfo.SdkOrderId = sdkOrderId;
                    trackingInfo.Nt8OrderId = null;
                    trackingInfo.OriginalRequest = request;
                    trackingInfo.CurrentState = OrderState.Pending;
                    trackingInfo.FilledQuantity = 0;
                    trackingInfo.AverageFillPrice = 0.0;
                    trackingInfo.LastUpdate = DateTime.UtcNow;
                    trackingInfo.ErrorMessage = null;

                    _orderTracking.Add(sdkOrderId, trackingInfo);

                    return trackingInfo;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Process order update callback from NinjaTrader 8.
        /// Called by NT8 strategy wrapper OnOrderUpdate.
        /// </summary>
        /// <param name="nt8OrderId">NT8 order ID.</param>
        /// <param name="sdkOrderId">SDK order ID.</param>
        /// <param name="orderState">NT8 order state string.</param>
        /// <param name="filled">Filled quantity.</param>
        /// <param name="averageFillPrice">Average fill price.</param>
        /// <param name="errorCode">Error code if rejected.</param>
        /// <param name="errorMessage">Error message if rejected.</param>
        public void ProcessOrderUpdate(
            string nt8OrderId,
            string sdkOrderId,
            string orderState,
            int filled,
            double averageFillPrice,
            int errorCode,
            string errorMessage)
        {
            if (string.IsNullOrWhiteSpace(sdkOrderId))
            {
                return;
            }

            try
            {
                lock (_lock)
                {
                    if (!_orderTracking.ContainsKey(sdkOrderId))
                    {
                        return;
                    }

                    var info = _orderTracking[sdkOrderId];

                    if (!string.IsNullOrWhiteSpace(nt8OrderId) && info.Nt8OrderId == null)
                    {
                        info.Nt8OrderId = nt8OrderId;
                        _nt8ToSdkOrderMap[nt8OrderId] = sdkOrderId;
                    }

                    info.CurrentState = MapNT8OrderState(orderState);
                    info.FilledQuantity = filled;
                    info.AverageFillPrice = averageFillPrice;
                    info.LastUpdate = DateTime.UtcNow;

                    if (errorCode != 0 && !string.IsNullOrWhiteSpace(errorMessage))
                    {
                        info.ErrorMessage = string.Format("[{0}] {1}", errorCode, errorMessage);
                        info.CurrentState = OrderState.Rejected;
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Process execution callback from NinjaTrader 8.
        /// Called by NT8 strategy wrapper OnExecutionUpdate.
        /// </summary>
        /// <param name="nt8OrderId">NT8 order ID.</param>
        /// <param name="executionId">Execution identifier.</param>
        /// <param name="price">Execution price.</param>
        /// <param name="quantity">Execution quantity.</param>
        /// <param name="time">Execution time.</param>
        public void ProcessExecution(
            string nt8OrderId,
            string executionId,
            double price,
            int quantity,
            DateTime time)
        {
            if (string.IsNullOrWhiteSpace(nt8OrderId))
            {
                return;
            }

            try
            {
                lock (_lock)
                {
                    if (!_nt8ToSdkOrderMap.ContainsKey(nt8OrderId))
                    {
                        return;
                    }

                    var sdkOrderId = _nt8ToSdkOrderMap[nt8OrderId];
                    if (!_orderTracking.ContainsKey(sdkOrderId))
                    {
                        return;
                    }

                    var info = _orderTracking[sdkOrderId];
                    info.LastUpdate = time;

                    if (info.FilledQuantity >= info.OriginalRequest.Quantity)
                    {
                        info.CurrentState = OrderState.Filled;
                    }
                    else if (info.FilledQuantity > 0)
                    {
                        info.CurrentState = OrderState.PartiallyFilled;
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Request to cancel an order.
        /// NOTE: Actual cancellation is performed by strategy wrapper code.
        /// </summary>
        /// <param name="sdkOrderId">SDK order ID to cancel.</param>
        /// <returns>True when cancel request is accepted; otherwise false.</returns>
        public bool CancelOrder(string sdkOrderId)
        {
            if (string.IsNullOrWhiteSpace(sdkOrderId))
            {
                throw new ArgumentNullException("sdkOrderId");
            }

            try
            {
                lock (_lock)
                {
                    if (!_orderTracking.ContainsKey(sdkOrderId))
                    {
                        return false;
                    }

                    var info = _orderTracking[sdkOrderId];
                    if (info.CurrentState == OrderState.Filled ||
                        info.CurrentState == OrderState.Cancelled ||
                        info.CurrentState == OrderState.Rejected)
                    {
                        return false;
                    }

                    info.LastUpdate = DateTime.UtcNow;
                    return true;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Get current status of an order.
        /// </summary>
        /// <param name="sdkOrderId">SDK order ID.</param>
        /// <returns>Order status snapshot; null when not found.</returns>
        public OrderStatus GetOrderStatus(string sdkOrderId)
        {
            if (string.IsNullOrWhiteSpace(sdkOrderId))
            {
                return null;
            }

            try
            {
                lock (_lock)
                {
                    if (!_orderTracking.ContainsKey(sdkOrderId))
                    {
                        return null;
                    }

                    var info = _orderTracking[sdkOrderId];
                    var status = new OrderStatus();
                    status.OrderId = info.SdkOrderId;
                    status.Symbol = info.OriginalRequest.Symbol;
                    status.Side = info.OriginalRequest.Side;
                    status.Quantity = info.OriginalRequest.Quantity;
                    status.Type = info.OriginalRequest.Type;
                    status.State = info.CurrentState;
                    status.FilledQuantity = info.FilledQuantity;
                    status.AverageFillPrice = info.FilledQuantity > 0 ? (decimal)info.AverageFillPrice : 0m;
                    status.CreatedTime = info.LastUpdate;
                    status.FilledTime = info.FilledQuantity > 0 ? (DateTime?)info.LastUpdate : null;
                    return status;
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Maps NinjaTrader order state string to SDK order state.
        /// </summary>
        /// <param name="nt8State">NT8 order state string.</param>
        /// <returns>Mapped SDK state.</returns>
        private OrderState MapNT8OrderState(string nt8State)
        {
            if (string.IsNullOrWhiteSpace(nt8State))
            {
                return OrderState.Expired;
            }

            switch (nt8State.ToUpperInvariant())
            {
                case "ACCEPTED":
                case "WORKING":
                    return OrderState.Working;

                case "FILLED":
                    return OrderState.Filled;

                case "PARTFILLED":
                case "PARTIALLYFILLED":
                    return OrderState.PartiallyFilled;

                case "CANCELLED":
                case "CANCELED":
                    return OrderState.Cancelled;

                case "REJECTED":
                    return OrderState.Rejected;

                case "PENDINGCANCEL":
                    return OrderState.Working;

                case "PENDINGCHANGE":
                case "PENDINGSUBMIT":
                    return OrderState.Pending;

                default:
                    return OrderState.Expired;
            }
        }
    }

    /// <summary>
    /// Internal tracking information for orders managed by NT8ExecutionAdapter.
    /// </summary>
    public class OrderTrackingInfo
    {
        /// <summary>
        /// SDK order identifier.
        /// </summary>
        public string SdkOrderId { get; set; }

        /// <summary>
        /// NinjaTrader order identifier.
        /// </summary>
        public string Nt8OrderId { get; set; }

        /// <summary>
        /// Original order request.
        /// </summary>
        public OrderRequest OriginalRequest { get; set; }

        /// <summary>
        /// Current SDK order state.
        /// </summary>
        public OrderState CurrentState { get; set; }

        /// <summary>
        /// Filled quantity.
        /// </summary>
        public int FilledQuantity { get; set; }

        /// <summary>
        /// Average fill price.
        /// </summary>
        public double AverageFillPrice { get; set; }

        /// <summary>
        /// Last update timestamp.
        /// </summary>
        public DateTime LastUpdate { get; set; }

        /// <summary>
        /// Last error message.
        /// </summary>
        public string ErrorMessage { get; set; }
    }
}
