using Orleans;
using static Managing.Common.Enums;

namespace Managing.Domain.Trades
{
    [GenerateSerializer]
    public sealed class ProfitAndLoss
    {
        [Id(0)]
        public decimal Realized { get; set; }

        [Id(1)]
        public decimal Net { get; set; }

        [Id(2)]
        public decimal AverageOpenPrice { get; private set; }
        private const decimal _multiplier = 100000;

        public ProfitAndLoss()
        {
        }

        public ProfitAndLoss(IEnumerable<Tuple<decimal, decimal>> initial, TradeDirection direction)
        {
            decimal buyQuantity = 0, sellQuantity = 0;
            decimal averageBuyPrice = 0, averageSellPrice = 0;
            foreach (var fill in initial)
            {
                var quantityFilled = fill.Item1;
                if (fill.Item1 > 0)
                {
                    buyQuantity += quantityFilled;
                    averageBuyPrice += quantityFilled * fill.Item2;
                }
                else if (fill.Item1 < 0)
                {
                    var absQuantity = Math.Abs(quantityFilled);
                    sellQuantity += absQuantity;
                    averageSellPrice += absQuantity * fill.Item2;
                }
            }

            if (buyQuantity > 0)
                averageBuyPrice /= buyQuantity;
            if (sellQuantity > 0)
                averageSellPrice /= sellQuantity;

            //buyQuantity = (buyQuantity / _multiplier);
            //sellQuantity = (sellQuantity / _multiplier);
            Net = buyQuantity - sellQuantity;
            AverageOpenPrice = Net > 0 ? averageBuyPrice : averageSellPrice;

            var absoluteRealized = (averageSellPrice - averageBuyPrice) * Math.Min(buyQuantity, sellQuantity);
            if (direction == TradeDirection.Long)
            {
                Realized = absoluteRealized;
            } else
            {
                Realized = -absoluteRealized;
            }
        }

        public void AddFill(decimal quantity, decimal price, TradeDirection direction)
        {
            if (quantity == 0)
                throw new ArgumentOutOfRangeException(nameof(quantity), "Quantity must be non-zero.");

            if (Math.Sign(Net) != Math.Sign(quantity))
            {
                decimal absNet = Math.Abs(Net);
                decimal absQuantity = Math.Abs(quantity);
                decimal realizedResult = 0;

                if (absNet == absQuantity) // flat
                {
                    realizedResult = (price - AverageOpenPrice) * Net;
                    AverageOpenPrice = 0;
                }
                else if (absNet > absQuantity) // decrease
                {
                    realizedResult = (price - AverageOpenPrice) * -quantity;
                }
                else // reverse
                {
                    realizedResult = (price - AverageOpenPrice) * Net;
                    AverageOpenPrice = price;
                }

                if (direction == TradeDirection.Long)
                    Realized += realizedResult;
                else
                    Realized += -realizedResult;
            }
            else // increase position
            {
                AverageOpenPrice = (Net * AverageOpenPrice + quantity * price) / (Net + quantity);
            }

            Net += quantity;
        }

        public decimal FloatingForTheoriticalExit(decimal exitPrice)
        {
            return (exitPrice - AverageOpenPrice) * Net;
        }
    }
}
