using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using NT8.Core.Common.Models;
using NT8.Core.OMS;

namespace NT8.Core.Execution
{
    /// <summary>
    /// Calculates R-value, R-multiple targets, and realized R performance for executions.
    /// </summary>
    public class RMultipleCalculator
    {
        private readonly ILogger _logger;
        private readonly object _lock = new object();
        private readonly Dictionary<string, double> _latestRValues;

        /// <summary>
        /// Initializes a new instance of the RMultipleCalculator class.
        /// </summary>
        /// <param name="logger">Logger instance.</param>
        public RMultipleCalculator(ILogger<RMultipleCalculator> logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _latestRValues = new Dictionary<string, double>();
        }

        /// <summary>
        /// Calculates monetary R-value for a position using stop distance, tick value, and contracts.
        /// </summary>
        /// <param name="position">Position information.</param>
        /// <param name="stopPrice">Stop price.</param>
        /// <param name="tickValue">Monetary value of one full point of price movement.</param>
        /// <returns>Total R-value in monetary terms for the position.</returns>
        /// <exception cref="ArgumentNullException">Thrown when position is null.</exception>
        /// <exception cref="ArgumentException">Thrown when inputs are invalid.</exception>
        public double CalculateRValue(Position position, double stopPrice, double tickValue)
        {
            if (position == null)
                throw new ArgumentNullException("position");

            try
            {
                if (position.Quantity == 0)
                    throw new ArgumentException("Position quantity cannot be zero", "position");
                if (tickValue <= 0)
                    throw new ArgumentException("Tick value must be positive", "tickValue");

                var stopDistance = System.Math.Abs(position.AveragePrice - stopPrice);
                if (stopDistance <= 0)
                    throw new ArgumentException("Stop distance must be positive", "stopPrice");

                var contracts = System.Math.Abs(position.Quantity);
                var rValue = stopDistance * tickValue * contracts;

                var cacheKey = String.Format("{0}:{1}", position.Symbol ?? "UNKNOWN", contracts);
                lock (_lock)
                {
                    _latestRValues[cacheKey] = rValue;
                }

                _logger.LogDebug("Calculated R-value for {Symbol}: distance={Distance:F4}, contracts={Contracts}, rValue={RValue:F4}",
                    position.Symbol, stopDistance, contracts, rValue);

                return rValue;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to calculate R-value: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates a target price from entry using R multiple and side.
        /// </summary>
        /// <param name="entryPrice">Entry price.</param>
        /// <param name="rValue">R-unit distance in price terms.</param>
        /// <param name="rMultiple">R multiple (for example 1.0, 2.0, 3.0).</param>
        /// <param name="side">Order side.</param>
        /// <returns>Calculated target price.</returns>
        /// <exception cref="ArgumentException">Thrown when inputs are invalid.</exception>
        public double CalculateTargetPrice(double entryPrice, double rValue, double rMultiple, OMS.OrderSide side)
        {
            try
            {
                if (entryPrice <= 0)
                    throw new ArgumentException("Entry price must be positive", "entryPrice");
                if (rValue <= 0)
                    throw new ArgumentException("R value must be positive", "rValue");
                if (rMultiple <= 0)
                    throw new ArgumentException("R multiple must be positive", "rMultiple");

                var distance = rValue * rMultiple;
                var target = side == OMS.OrderSide.Buy ? entryPrice + distance : entryPrice - distance;

                _logger.LogDebug("Calculated target price: entry={Entry:F4}, rValue={RValue:F4}, rMultiple={RMultiple:F2}, side={Side}, target={Target:F4}",
                    entryPrice, rValue, rMultiple, side, target);

                return target;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to calculate target price: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates realized R-multiple for a completed trade.
        /// </summary>
        /// <param name="entryPrice">Entry price.</param>
        /// <param name="exitPrice">Exit price.</param>
        /// <param name="rValue">R-unit distance in price terms.</param>
        /// <returns>Realized R-multiple.</returns>
        /// <exception cref="ArgumentException">Thrown when inputs are invalid.</exception>
        public double CalculateRMultiple(double entryPrice, double exitPrice, double rValue)
        {
            try
            {
                if (entryPrice <= 0)
                    throw new ArgumentException("Entry price must be positive", "entryPrice");
                if (exitPrice <= 0)
                    throw new ArgumentException("Exit price must be positive", "exitPrice");
                if (rValue <= 0)
                    throw new ArgumentException("R value must be positive", "rValue");

                var pnlDistance = exitPrice - entryPrice;
                var rMultiple = pnlDistance / rValue;

                _logger.LogDebug("Calculated realized R-multiple: entry={Entry:F4}, exit={Exit:F4}, rValue={RValue:F4}, rMultiple={RMultiple:F4}",
                    entryPrice, exitPrice, rValue, rMultiple);

                return rMultiple;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to calculate realized R-multiple: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Creates up to three tick-based targets from R multiples and stop distance.
        /// </summary>
        /// <param name="entryPrice">Entry price.</param>
        /// <param name="stopPrice">Stop price.</param>
        /// <param name="rMultiples">Array of R multiples (first three values are used).</param>
        /// <returns>Multi-level target configuration.</returns>
        /// <exception cref="ArgumentNullException">Thrown when rMultiples is null.</exception>
        /// <exception cref="ArgumentException">Thrown when inputs are invalid.</exception>
        public MultiLevelTargets CreateRBasedTargets(double entryPrice, double stopPrice, double[] rMultiples)
        {
            if (rMultiples == null)
                throw new ArgumentNullException("rMultiples");

            try
            {
                if (entryPrice <= 0)
                    throw new ArgumentException("Entry price must be positive", "entryPrice");

                var baseRiskTicks = System.Math.Abs(entryPrice - stopPrice);
                if (baseRiskTicks <= 0)
                    throw new ArgumentException("Stop price must differ from entry price", "stopPrice");

                if (rMultiples.Length == 0)
                    throw new ArgumentException("At least one R multiple is required", "rMultiples");

                var tp1Ticks = ToTargetTicks(baseRiskTicks, rMultiples, 0);
                var tp2Ticks = rMultiples.Length > 1 ? (int?)ToTargetTicks(baseRiskTicks, rMultiples, 1) : null;
                var tp3Ticks = rMultiples.Length > 2 ? (int?)ToTargetTicks(baseRiskTicks, rMultiples, 2) : null;

                var targets = new MultiLevelTargets(
                    tp1Ticks,
                    1,
                    tp2Ticks,
                    tp2Ticks.HasValue ? (int?)1 : null,
                    tp3Ticks,
                    tp3Ticks.HasValue ? (int?)1 : null);

                _logger.LogDebug("Created R-based targets: riskTicks={RiskTicks:F4}, TP1={TP1}, TP2={TP2}, TP3={TP3}",
                    baseRiskTicks, tp1Ticks, tp2Ticks.HasValue ? tp2Ticks.Value : 0, tp3Ticks.HasValue ? tp3Ticks.Value : 0);

                return targets;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to create R-based targets: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets the last cached R-value for a symbol and quantity pair.
        /// </summary>
        /// <param name="symbol">Symbol.</param>
        /// <param name="quantity">Absolute contract quantity.</param>
        /// <returns>Cached R-value if available; otherwise null.</returns>
        public double? GetLatestRValue(string symbol, int quantity)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                if (quantity <= 0)
                    throw new ArgumentException("Quantity must be positive", "quantity");

                var cacheKey = String.Format("{0}:{1}", symbol, quantity);

                lock (_lock)
                {
                    if (_latestRValues.ContainsKey(cacheKey))
                        return _latestRValues[cacheKey];
                }

                return null;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get cached R-value: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Clears all cached R-values.
        /// </summary>
        public void ClearCache()
        {
            try
            {
                lock (_lock)
                {
                    _latestRValues.Clear();
                }

                _logger.LogDebug("Cleared R-value cache");
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to clear R-value cache: {0}", ex.Message);
                throw;
            }
        }

        private int ToTargetTicks(double baseRiskTicks, double[] rMultiples, int index)
        {
            if (index < 0 || index >= rMultiples.Length)
                throw new ArgumentOutOfRangeException("index");

            var multiple = rMultiples[index];
            if (multiple <= 0)
                throw new ArgumentException("R multiple must be positive", "rMultiples");

            var rawTicks = baseRiskTicks * multiple;
            var rounded = (int)System.Math.Round(rawTicks, MidpointRounding.AwayFromZero);

            if (rounded <= 0)
                rounded = 1;

            return rounded;
        }
    }
}
