using System;
using System.Threading;

using SPTraderStandardLib;
using SPTraderStandardLib.Models;
using SPTraderStandardLib.Common;
using SPTraderStandardLib.Interface;

using Newtonsoft.Json;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Autofac;
using Autofac.Extras.Moq;
using Moq;

namespace SPCoreUnitTest
{
    [TestClass()]
    public class TestMethods : IDisposable, IWebSocketHeartBeat, IServerResult, IOrderListener
    {
        private ClientPortalApi obj = null;
        private readonly string targetAccNo = SPConstants.targetAccNo;
        private readonly string userAccName = SPConstants.userAccName;
        private readonly string userAccPass = SPConstants.userAccPass;
        private readonly string prodCode = SPConstants.prodCode;
        private readonly string targetSysId = SPConstants.targetSysId;

        private ManualResetEvent addEvent = null;

        private bool once = false;

        [AssemblyInitialize()]
        public static void AssemblyInit(TestContext context)
        {
            Console.WriteLine(context.TestName);
        }

        [ClassInitialize()]         // calls once per instance
        public static void ClassInit(TestContext context) { }

        // Real constructor
        public TestMethods()
        {
            obj = new ClientPortalApi();

            Console.WriteLine("accessRight/UserLogin");
            var t0 = obj.accessRight.UserLogin(userAccName, userAccPass);
            Console.WriteLine(t0.ToJsonString(Formatting.Indented));
        }

        [TestInitialize()]
        public void SetUp()         // calls before every test case
        {
            if (!once)
            {
                obj.webSocketData.RegisterListener((IWebSocketHeartBeat)this);
                obj.webSocketData.RegisterListener((IServerResult)this);
                obj.webSocketData.RegisterListener((IOrderListener)this);

                obj.webSocketData.Connect();
                obj.webSocketData.SubscribeEvents(DataMaskType.All);
                once = true;
            }
        }

        [TestCleanup()]
        public void TearDown() { }  // calls after every test case

        [ClassCleanup()]            // calls once per instance
        public static void OneTimeTearDown() { }

        // real cleanup
        public void Dispose()
        {
            Console.WriteLine("accessRight/SessionTokenHeartbeat");
            if (obj.accessRight.SessionTokenHeartbeat())
            {
                Console.WriteLine("accessRight/UserLogout");
                if (obj.accessRight.UserLogout())
                    Console.WriteLine("Done.");
            }
        }

        #region Account Related
        [TestMethod]
        public void AccountInfo()
        {
            Console.WriteLine("account/AccountInfo");
            var t1 = obj.account.AccountInfo(targetAccNo);
            Console.WriteLine(t1.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void AccountSummary()
        {
            Console.WriteLine("account/AccountSummary");
            var t2 = obj.account.AccountSummary(targetAccNo);
            Console.WriteLine(t2.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void AccountPosition()
        {
            Console.WriteLine("account/AccountPosition");
            var t3 = obj.account.AccountPosition(targetAccNo);
            Console.WriteLine(t3.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void AccountOrder()
        {
            Console.WriteLine("account/AccountOrder");
            var t4 = obj.account.AccountOrder(targetAccNo);
            Console.WriteLine(t4.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void AccountBalance()
        {
            Console.WriteLine("account/AccountBalance");
            var t5 = obj.account.AccountBalance(targetAccNo);
            Console.WriteLine(t5.ToJsonString(Formatting.Indented));
        }
        #endregion

        #region Reporting Related
        [TestMethod]
        public void CashMovementList()
        {
            Console.WriteLine("reporting/CashMovementList");
            var t1 = obj.reporting.CashMovementList("D", targetAccNo, "2021-05-01", "2020-12-06");
            Console.WriteLine(t1.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void OrderHist()
        {
            Console.WriteLine("reporting/OrderHist");
            var req = new RestOrderHistReq()
            {
                targetAccNo = targetAccNo,
                targetSystemId = targetSysId,
                sessionToken = obj.accessRight.loginResp.sessionToken
            };
            var t2 = obj.reporting.OrderHist(req);
            Console.WriteLine(t2.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void DoneTrade()
        {
            Console.WriteLine("reporting/DoneTrade");
            var reqdone = new RestDoneTradeReq()
            {
                targetAccNo = targetAccNo,
                targetSystemId = targetSysId,
                sessionToken = obj.accessRight.loginResp.sessionToken
            };
            var t3 = obj.reporting.DoneTrade(reqdone);
            Console.WriteLine(t3.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void UserLog()
        {
            Console.WriteLine("reporting/UserLog");
            var requser = new RestUserLogReq()
            {
                logUserId = userAccName,
                accNo = targetAccNo,
                targetSystemId = targetSysId,
                sessionToken = obj.accessRight.loginResp.sessionToken
            };
            var t4 = obj.reporting.UserLog(requser);
            Console.WriteLine(t4.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void WorkingOrder()
        {
            var req = new RestOrderHistReq()
            {
                targetAccNo = targetAccNo,
                targetSystemId = targetSysId,
                sessionToken = obj.accessRight.loginResp.sessionToken
            };

            Console.WriteLine("reporting/WorkingOrder");
            var t5 = obj.reporting.WorkingOrder(req);
            Console.WriteLine(t5.ToJsonString(Formatting.Indented));
        }
        #endregion

        #region Order Related
        private RestOrderResp AddOrder(double price, string exOrderNo)
        {
            var neword = new RestOrderReq
            {
                accNo = targetAccNo,
                buySell = "B",
                //clOrderId = "123456789ASDA",
                condType = 0,
                extOrderNo = exOrderNo,
                priceInDec = price,
                qty = 1,
                // options = 1,    // T+1
                // orderType = 6,  // Market order
                orderType = 0,
                validType = 0,
            };

            return obj.order.Add(neword);
        }

        [DataTestMethod()]
        [DataRow(27000.00, "12345555")]
        [DataRow(27200.00, "12345666")]
        [DataRow(27400.00, "12345777")]
        public void Add(double price, string exOrdNo)
        {
            addEvent = new ManualResetEvent(false);

            Console.WriteLine("order/Add");
            var t1 = AddOrder(price, exOrdNo);
            Console.WriteLine(t1.ToJsonString(Formatting.Indented));

            var res = addEvent.WaitOne();

            Assert.IsTrue(res);
        }

        [DataTestMethod()]
        [DataRow(27000.00, "12346666")]
        [DataRow(27200.00, "12346777")]
        [DataRow(27400.00, "12346888")]
        public void Change(double price, string exOrdNo)
        {
            Console.WriteLine("order/Add");
            var t1 = AddOrder(price, exOrdNo);
            Console.WriteLine(t1.ToJsonString(Formatting.Indented));

            var chgord = new RestOrdExtReq
            {
                accNo = t1.accNo,
                accOrderNo = t1.accOrderNo,
                buySell = t1.buySell,
                priceInDec = t1.tradePrice + 100.0,
                qty = t1.qty,
                extOrderNo = t1.extOrderNo,
                prodCode = t1.prodCode,
            };

            Console.WriteLine("order/Change");
            var t2 = obj.order.Change(chgord);
            Console.WriteLine(t2.ToJsonString(Formatting.Indented));
        }

        [TestMethod]
        public void Delete()
        {
            //var delord = new RestOrdReq
            //{
            //    accNo = t1.accNo,
            //    accOrderNo = t1.accOrderNo,
            //    buySell = t1.buySell,
            //    extOrderNo = t1.extOrderNo,
            //    prodCode = t1.prodCode,
            //};

            //Console.WriteLine("order/Delete");
            //var t3 = dd.order.Delete(delord);
            //Console.WriteLine(t3.ToJsonString(Formatting.Indented));
        }
        #endregion

        #region Callbacks
        public void OnHeartBeatReceived(ServerPingPong data)
        {
            // Act
            Console.WriteLine(data.ToJsonString());

            // Assert
            Assert.IsTrue(data.last_ping > 0);
        }

        public void OnRequestResultReponse(ServerResult data)
        {
            Console.WriteLine(data.ToJsonString());

            // Assert
            Assert.IsTrue(data.result_code.Equals("0"));
        }

        public void OnOrderResponse(ServerAccountOrder data)
        {
            // Act
            Console.WriteLine(data.ToJsonString());
            addEvent?.Set();

            // Assert
            Assert.IsFalse(string.IsNullOrEmpty(data.clOrderId));
        }
        #endregion

        #region Mock Demo
        // https://www.youtube.com/watch?v=DwbYxP-etMY

        [TestMethod]
        public void TestMock_Callback()
        {
            using (var mock = AutoMock.GetLoose())
            {
                var pp = new PriceResponse() { Open = 2.5M };
                mock.Mock<IPriceListener>()
                    .Setup(x => x.OnPriceUpdate(pp))
                    .Callback<PriceResponse>(MockPriceUpdate);

                // TOBE: completed, not sure how to mock callbacks
                //var loginResp = obj.accessRight.loginResp;
                //var dd = new Mock<MarketData>(loginResp.userId, loginResp.spServerKey);
                //dd.Setup(x => x.RegisterListener((IPriceListener) mock));
            }
        }

        private void MockPriceUpdate(PriceResponse data) { }

        [TestMethod]
        public void TestMock_Methods()
        {
            using (var mock = AutoMock.GetLoose())
            {
                // Arrange
                var cls = mock.Create<ClientPortalApi>();

                // Act
                var expected = true;
                var actual = cls.Foo();

                // Assert
                Assert.AreEqual(expected, actual);
            }
        }
        #endregion
    }
}