using System;

namespace NT8.Core.Execution
{
    /// <summary>
    /// Calculates various types of slippage for order executions
    /// </summary>
    public class SlippageCalculator
    {
        /// <summary>
        /// Calculate price slippage between intended and actual execution
        /// </summary>
        /// <param name="orderType">Type of order</param>
        /// <param name="intendedPrice">Price the order was intended to execute at</param>
        /// <param name="fillPrice">Actual fill price</param>
        /// <returns>Calculated slippage value</returns>
        public decimal CalculateSlippage(OMS.OrderType orderType, decimal intendedPrice, decimal fillPrice)
        {
            try
            {
                // Slippage is calculated as fillPrice - intendedPrice
                // For market orders, compare to market price at submission
                // For limit orders, compare to limit price
                // For stop orders, compare to stop price
                // For stop-limit orders, compare to trigger price
                return fillPrice - intendedPrice;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to calculate slippage: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Classifies slippage as positive, negative, or zero based on side and execution
        /// </summary>
        /// <param name="slippage">Calculated slippage value</param>
        /// <param name="side">Order side (buy/sell)</param>
        /// <returns>Type of slippage</returns>
        public SlippageType ClassifySlippage(decimal slippage, OMS.OrderSide side)
        {
            try
            {
                // For buys: positive slippage is bad (paid more than expected), negative is good (paid less)
                // For sells: positive slippage is good (got more than expected), negative is bad (got less)
                if (slippage > 0)
                {
                    if (side == OMS.OrderSide.Buy)
                        return SlippageType.Negative; // Paid more than expected on buy
                    else
                        return SlippageType.Positive; // Got more than expected on sell
                }
                else if (slippage < 0)
                {
                    if (side == OMS.OrderSide.Buy)
                        return SlippageType.Positive; // Paid less than expected on buy
                    else
                        return SlippageType.Negative; // Got less than expected on sell
                }
                else
                {
                    return SlippageType.Zero; // No slippage
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to classify slippage: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Converts slippage value to equivalent number of ticks
        /// </summary>
        /// <param name="slippage">Slippage value to convert</param>
        /// <param name="tickSize">Size of one tick for the instrument</param>
        /// <returns>Number of ticks equivalent to the slippage</returns>
        public int SlippageInTicks(decimal slippage, decimal tickSize)
        {
            if (tickSize <= 0)
                throw new ArgumentException("Tick size must be positive", "tickSize");

            try
            {
                return (int)Math.Round((double)(Math.Abs(slippage) / tickSize));
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to convert slippage to ticks: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Calculates the financial impact of slippage on P&L
        /// </summary>
        /// <param name="slippage">Slippage value</param>
        /// <param name="quantity">Order quantity</param>
        /// <param name="tickValue">Value of one tick</param>
        /// <param name="side">Order side</param>
        /// <returns>Financial impact of slippage</returns>
        public decimal SlippageImpact(decimal slippage, int quantity, decimal tickValue, OMS.OrderSide side)
        {
            if (quantity <= 0)
                throw new ArgumentException("Quantity must be positive", "quantity");
            if (tickValue <= 0)
                throw new ArgumentException("Tick value must be positive", "tickValue");

            try
            {
                // Calculate impact in terms of ticks
                var slippageInTicks = slippage / tickValue;

                // Impact = slippage_in_ticks * quantity * tick_value
                // For buys: positive slippage (paid more) is negative impact, negative slippage (paid less) is positive impact
                // For sells: positive slippage (got more) is positive impact, negative slippage (got less) is negative impact
                var impact = slippageInTicks * quantity * tickValue;

                // Adjust sign based on order side and slippage classification
                if (side == OMS.OrderSide.Buy)
                {
                    // For buys, worse execution (positive slippage) is negative impact
                    return -impact;
                }
                else
                {
                    // For sells, better execution (negative slippage) is positive impact
                    return impact;
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to calculate slippage impact: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Calculates percentage slippage relative to intended price
        /// </summary>
        /// <param name="slippage">Calculated slippage</param>
        /// <param name="intendedPrice">Intended execution price</param>
        /// <returns>Percentage slippage</returns>
        public decimal CalculatePercentageSlippage(decimal slippage, decimal intendedPrice)
        {
            if (intendedPrice <= 0)
                throw new ArgumentException("Intended price must be positive", "intendedPrice");

            try
            {
                return (slippage / intendedPrice) * 100;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to calculate percentage slippage: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Determines if slippage is within acceptable bounds
        /// </summary>
        /// <param name="slippage">Calculated slippage</param>
        /// <param name="maxAcceptableSlippage">Maximum acceptable slippage in ticks</param>
        /// <param name="tickSize">Size of one tick</param>
        /// <returns>True if slippage is within acceptable bounds</returns>
        public bool IsSlippageAcceptable(decimal slippage, int maxAcceptableSlippage, decimal tickSize)
        {
            if (tickSize <= 0)
                throw new ArgumentException("Tick size must be positive", "tickSize");

            try
            {
                var slippageInTicks = SlippageInTicks(slippage, tickSize);
                return Math.Abs(slippageInTicks) <= maxAcceptableSlippage;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to evaluate slippage acceptability: {0}", ex.Message), ex);
            }
        }

        /// <summary>
        /// Calculates effective cost of slippage in basis points
        /// </summary>
        /// <param name="slippage">Calculated slippage</param>
        /// <param name="intendedPrice">Intended execution price</param>
        /// <returns>Cost of slippage in basis points</returns>
        public decimal SlippageInBasisPoints(decimal slippage, decimal intendedPrice)
        {
            if (intendedPrice <= 0)
                throw new ArgumentException("Intended price must be positive", "intendedPrice");

            try
            {
                // Basis points = percentage * 100
                var percentage = (Math.Abs(slippage) / intendedPrice) * 100;
                return percentage * 100;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(String.Format("Failed to calculate slippage in basis points: {0}", ex.Message), ex);
            }
        }
    }
}
