using Managing.Domain.Trades;
using Managing.Domain.Users;
using Xunit;
using static Managing.Common.Enums;

namespace Managing.Application.Tests
{
    public class ProfitAndLossTests
    {
        [Theory]
        [InlineData(1, 100, 110, 10)]
        [InlineData(2, 100, 110, 20)]
        public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice,
            decimal expectedResult)
        {
            // Arrange 
            var init = new List<Tuple<decimal, decimal>>();
            init.Add(new Tuple<decimal, decimal>(quantity, price));

            var pnl = new ProfitAndLoss(init, TradeDirection.Long);

            // Act 
            var result = pnl.FloatingForTheoriticalExit(exitPrice);


            // Assert
            Assert.Equal(expectedResult, result);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit()
        {
            // Arrange
            var position = GetFakeShortPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Trigger Stop Loss
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
                position.OriginDirection);

            // Assert 
            Assert.Equal(20, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit()
        {
            // Arrange
            var position = GetSolanaLongPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Trigger Stop Loss
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
                position.OriginDirection);

            // Assert 
            Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Long_Solana_failed_Position_After_Took_One_Profit2()
        {
            // Arrange
            var position = GetSolanaLongPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit2.Quantity, position.StopLoss.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Assert 
            Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit2()
        {
            // Arrange
            var position = GetFakeShortPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit2.Quantity, position.StopLoss.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Assert 
            Assert.Equal(20, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Short_failed_Position_After_Took_One_Profit3()
        {
            // Arrange
            var position = GetFakeShortPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
            position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price,
                TradeDirection.Short);
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
                TradeDirection.Short);

            // Assert 
            Assert.Equal(20, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Short_Succeeded_Position_After_Two_Take_Profit()
        {
            // Arrange
            var position = GetFakeShortPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Trigger Stop Loss
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
                TradeDirection.Short);

            // Assert 
            Assert.Equal(120, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Long_failed_Position_After_Took_One_Profit()
        {
            // Arrange
            var position = GetFakeLongPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Trigger Stop Loss
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
                TradeDirection.Long);

            // Assert 
            Assert.Equal(20, position.ProfitAndLoss.Realized);
        }

        [Fact]
        public void Should_Return_Correct_Pnl_For_Long_Succeeded_Position_After_Two_Take_Profit()
        {
            // Arrange
            var position = GetFakeLongPosition();

            // Setup Open and first take profit
            var orders = new List<Tuple<decimal, decimal>>
            {
                new Tuple<decimal, decimal>(position.Open.Quantity, position.Open.Price),
                new Tuple<decimal, decimal>(-position.TakeProfit1.Quantity, position.TakeProfit1.Price)
            };

            // Act
            position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);

            // Trigger Stop Loss
            position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
                TradeDirection.Long);

            // Assert 
            Assert.Equal(120, position.ProfitAndLoss.Realized);
        }

        private static Position GetFakeShortPosition()
        {
            return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Short, Ticker.BTC, null,
                PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
            {
                Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
                    TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
                StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.StopMarket, Ticker.ADA, 10, 110, 1, "StopLossOrderId", ""),
                TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, 6, 90, 1, "TakeProfit1OrderId", ""),
                TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, 4, 85, 1, "TakeProfit1OrderId", "")
            };
        }

        private static Position GetSolanaLongPosition()
        {
            return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
                PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
            {
                Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled,
                    TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1,
                    "OpenOrderId", ""),
                StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
                    TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1,
                    "StopLossOrderId", ""),
                TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000,
                    1, "TakeProfit1OrderId", ""),
                TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000,
                    1, "TakeProfit1OrderId", "")
            };
        }

        private static Position GetFakeLongPosition()
        {
            return new Position(Guid.NewGuid(), "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
                PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
            {
                Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
                    TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
                StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.StopMarket, Ticker.ADA, 10, 90, 1, "StopLossOrderId", ""),
                TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, 6, 110, 1, "TakeProfit1OrderId", ""),
                TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
                    TradeType.TakeProfitLimit, Ticker.ADA, 4, 115, 1, "TakeProfit1OrderId", "")
            };
        }
    }
}