using System;
using System.Collections.Generic;
using System.Linq;
using NT8.Core.Logging;

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Confidence interval model.
    /// </summary>
    public class ConfidenceInterval
    {
        public double ConfidenceLevel { get; set; }
        public double LowerBound { get; set; }
        public double UpperBound { get; set; }
    }

    /// <summary>
    /// Monte Carlo simulation output.
    /// </summary>
    public class MonteCarloResult
    {
        public int NumSimulations { get; set; }
        public int NumTradesPerSimulation { get; set; }
        public List<double> FinalPnLDistribution { get; set; }
        public List<double> MaxDrawdownDistribution { get; set; }
        public double MeanFinalPnL { get; set; }

        public MonteCarloResult()
        {
            FinalPnLDistribution = new List<double>();
            MaxDrawdownDistribution = new List<double>();
        }
    }

    /// <summary>
    /// Monte Carlo simulator for PnL scenarios.
    /// </summary>
    public class MonteCarloSimulator
    {
        private readonly ILogger _logger;
        private readonly Random _random;

        public MonteCarloSimulator(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _random = new Random(1337);
        }

        /// <summary>
        /// Runs Monte Carlo simulation using bootstrap trade sampling.
        /// </summary>
        public MonteCarloResult Simulate(List<TradeRecord> historicalTrades, int numSimulations, int numTrades)
        {
            if (historicalTrades == null)
                throw new ArgumentNullException("historicalTrades");
            if (numSimulations <= 0)
                throw new ArgumentException("numSimulations must be positive", "numSimulations");
            if (numTrades <= 0)
                throw new ArgumentException("numTrades must be positive", "numTrades");
            if (historicalTrades.Count == 0)
                throw new ArgumentException("historicalTrades cannot be empty", "historicalTrades");

            try
            {
                var result = new MonteCarloResult();
                result.NumSimulations = numSimulations;
                result.NumTradesPerSimulation = numTrades;

                for (var sim = 0; sim < numSimulations; sim++)
                {
                    var equity = 0.0;
                    var peak = 0.0;
                    var maxDd = 0.0;

                    for (var i = 0; i < numTrades; i++)
                    {
                        var sample = historicalTrades[_random.Next(historicalTrades.Count)];
                        equity += sample.RealizedPnL;

                        if (equity > peak)
                            peak = equity;

                        var dd = peak - equity;
                        if (dd > maxDd)
                            maxDd = dd;
                    }

                    result.FinalPnLDistribution.Add(equity);
                    result.MaxDrawdownDistribution.Add(maxDd);
                }

                result.MeanFinalPnL = result.FinalPnLDistribution.Average();
                return result;
            }
            catch (Exception ex)
            {
                _logger.LogError("Monte Carlo simulate failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates risk of ruin as probability max drawdown exceeds threshold.
        /// </summary>
        public double CalculateRiskOfRuin(List<TradeRecord> trades, double drawdownThreshold)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");
            if (drawdownThreshold <= 0)
                throw new ArgumentException("drawdownThreshold must be positive", "drawdownThreshold");

            try
            {
                var result = Simulate(trades, 2000, Math.Max(30, trades.Count));
                var ruined = result.MaxDrawdownDistribution.Count(d => d >= drawdownThreshold);
                return (double)ruined / result.MaxDrawdownDistribution.Count;
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculateRiskOfRuin failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates confidence interval for final PnL distribution.
        /// </summary>
        public ConfidenceInterval CalculateConfidenceInterval(MonteCarloResult result, double confidenceLevel)
        {
            if (result == null)
                throw new ArgumentNullException("result");
            if (confidenceLevel <= 0.0 || confidenceLevel >= 1.0)
                throw new ArgumentException("confidenceLevel must be in (0,1)", "confidenceLevel");

            try
            {
                var sorted = result.FinalPnLDistribution.OrderBy(v => v).ToList();
                if (sorted.Count == 0)
                    return new ConfidenceInterval { ConfidenceLevel = confidenceLevel, LowerBound = 0.0, UpperBound = 0.0 };

                var alpha = 1.0 - confidenceLevel;
                var lowerIndex = (int)Math.Floor((alpha / 2.0) * (sorted.Count - 1));
                var upperIndex = (int)Math.Floor((1.0 - (alpha / 2.0)) * (sorted.Count - 1));

                return new ConfidenceInterval
                {
                    ConfidenceLevel = confidenceLevel,
                    LowerBound = sorted[Math.Max(0, lowerIndex)],
                    UpperBound = sorted[Math.Min(sorted.Count - 1, upperIndex)]
                };
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculateConfidenceInterval failed: {0}", ex.Message);
                throw;
            }
        }
    }
}
