using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NT8.Adapters.NinjaTrader;
using NT8.Core.OMS;

namespace NT8.Core.Tests.Adapters
{
    /// <summary>
    /// Unit tests for NT8ExecutionAdapter.
    /// </summary>
    [TestClass]
    public class NT8ExecutionAdapterTests
    {
        [TestMethod]
        public void SubmitOrder_WithValidRequest_ShouldCreateTrackingInfo()
        {
            var adapter = new NT8ExecutionAdapter();
            var request = CreateRequest();

            var info = adapter.SubmitOrder(request, "SDK-1");

            Assert.IsNotNull(info);
            Assert.AreEqual("SDK-1", info.SdkOrderId);
            Assert.AreEqual(OrderState.Pending, info.CurrentState);
            Assert.AreEqual(0, info.FilledQuantity);
        }

        [TestMethod]
        public void SubmitOrder_WithDuplicateOrderId_ShouldThrowInvalidOperationException()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");

            Assert.ThrowsException<InvalidOperationException>(
                delegate
                {
                    adapter.SubmitOrder(CreateRequest(), "SDK-1");
                });
        }

        [TestMethod]
        public void SubmitOrder_WithNullRequest_ShouldThrowArgumentNullException()
        {
            var adapter = new NT8ExecutionAdapter();

            Assert.ThrowsException<ArgumentNullException>(
                delegate
                {
                    adapter.SubmitOrder(null, "SDK-1");
                });
        }

        [TestMethod]
        public void ProcessOrderUpdate_WithWorkingState_ShouldUpdateState()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 0, null);

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.IsNotNull(status);
            Assert.AreEqual(OrderState.Working, status.State);
        }

        [TestMethod]
        public void ProcessOrderUpdate_WithFilledState_ShouldMarkFilled()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(2), "SDK-1");

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 2, 4205.25, 0, null);

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.Filled, status.State);
            Assert.AreEqual(2, status.FilledQuantity);
        }

        [TestMethod]
        public void ProcessOrderUpdate_WithRejection_ShouldSetRejectedState()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 123, "Rejected by broker");

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.Rejected, status.State);
        }

        [TestMethod]
        public void ProcessExecution_WithFullFill_ShouldMarkFilled()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(3), "SDK-1");
            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 3, 4202.0, 0, null);

            adapter.ProcessExecution("NT8-1", "EX-1", 4202.0, 3, DateTime.UtcNow);

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.Filled, status.State);
        }

        [TestMethod]
        public void ProcessExecution_WithPartialFill_ShouldMarkPartiallyFilled()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(3), "SDK-1");
            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 1, 4202.0, 0, null);

            adapter.ProcessExecution("NT8-1", "EX-1", 4202.0, 1, DateTime.UtcNow);

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.PartiallyFilled, status.State);
        }

        [TestMethod]
        public void CancelOrder_WithWorkingOrder_ShouldReturnTrue()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");
            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "WORKING", 0, 0.0, 0, null);

            var result = adapter.CancelOrder("SDK-1");

            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CancelOrder_WithFilledOrder_ShouldReturnFalse()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");
            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 1, 4202.0, 0, null);

            var result = adapter.CancelOrder("SDK-1");

            Assert.IsFalse(result);
        }

        [TestMethod]
        public void GetOrderStatus_WithExistingOrder_ShouldReturnStatus()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");

            var status = adapter.GetOrderStatus("SDK-1");

            Assert.IsNotNull(status);
            Assert.AreEqual("SDK-1", status.OrderId);
            Assert.AreEqual("ES", status.Symbol);
        }

        [TestMethod]
        public void GetOrderStatus_WithNonExistentOrder_ShouldReturnNull()
        {
            var adapter = new NT8ExecutionAdapter();

            var status = adapter.GetOrderStatus("MISSING");

            Assert.IsNull(status);
        }

        [DataTestMethod]
        [DataRow("ACCEPTED", OrderState.Working)]
        [DataRow("FILLED", OrderState.Filled)]
        [DataRow("CANCELLED", OrderState.Cancelled)]
        [DataRow("REJECTED", OrderState.Rejected)]
        public void MapNT8OrderState_WithKnownStates_ShouldMapCorrectly(string nt8State, OrderState expected)
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(), "SDK-1");

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", nt8State, 0, 0.0, 0, null);

            var status = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(expected, status.State);
        }

        [TestMethod]
        public void SubmitOrder_WithConcurrentCalls_ShouldBeThreadSafe()
        {
            var adapter = new NT8ExecutionAdapter();
            var tasks = new List<Task>();
            var count = 50;

            for (var i = 0; i < count; i++)
            {
                var index = i;
                tasks.Add(Task.Run(
                    delegate
                    {
                        adapter.SubmitOrder(CreateRequest(), string.Format("SDK-{0}", index));
                    }));
            }

            Task.WaitAll(tasks.ToArray());

            for (var i = 0; i < count; i++)
            {
                var status = adapter.GetOrderStatus(string.Format("SDK-{0}", i));
                Assert.IsNotNull(status);
            }
        }

        [TestMethod]
        public void ProcessExecution_WithMultipleCallsForSameOrder_ShouldAccumulate()
        {
            var adapter = new NT8ExecutionAdapter();
            adapter.SubmitOrder(CreateRequest(3), "SDK-1");

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "PARTFILLED", 1, 4201.0, 0, null);
            adapter.ProcessExecution("NT8-1", "EX-1", 4201.0, 1, DateTime.UtcNow);

            var statusAfterFirst = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.PartiallyFilled, statusAfterFirst.State);

            adapter.ProcessOrderUpdate("NT8-1", "SDK-1", "FILLED", 3, 4202.0, 0, null);
            adapter.ProcessExecution("NT8-1", "EX-2", 4202.0, 2, DateTime.UtcNow);

            var statusAfterSecond = adapter.GetOrderStatus("SDK-1");
            Assert.AreEqual(OrderState.Filled, statusAfterSecond.State);
            Assert.AreEqual(3, statusAfterSecond.FilledQuantity);
        }

        private static OrderRequest CreateRequest()
        {
            return CreateRequest(1);
        }

        private static OrderRequest CreateRequest(int quantity)
        {
            var request = new OrderRequest();
            request.Symbol = "ES";
            request.Side = OrderSide.Buy;
            request.Type = OrderType.Market;
            request.Quantity = quantity;
            request.TimeInForce = TimeInForce.Day;
            request.ClientOrderId = Guid.NewGuid().ToString();
            return request;
        }
    }
}
