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

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Grade-level aggregate analysis report.
    /// </summary>
    public class GradePerformanceReport
    {
        /// <summary>
        /// Metrics by grade.
        /// </summary>
        public Dictionary<TradeGrade, PerformanceMetrics> MetricsByGrade { get; set; }

        /// <summary>
        /// Accuracy by grade.
        /// </summary>
        public Dictionary<TradeGrade, double> GradeAccuracy { get; set; }

        /// <summary>
        /// Suggested threshold.
        /// </summary>
        public TradeGrade SuggestedThreshold { get; set; }

        /// <summary>
        /// Creates a report instance.
        /// </summary>
        public GradePerformanceReport()
        {
            MetricsByGrade = new Dictionary<TradeGrade, PerformanceMetrics>();
            GradeAccuracy = new Dictionary<TradeGrade, double>();
            SuggestedThreshold = TradeGrade.F;
        }
    }

    /// <summary>
    /// Analyzes performance by confluence grade.
    /// </summary>
    public class GradePerformanceAnalyzer
    {
        private readonly ILogger _logger;
        private readonly PerformanceCalculator _calculator;

        /// <summary>
        /// Initializes analyzer.
        /// </summary>
        /// <param name="logger">Logger dependency.</param>
        public GradePerformanceAnalyzer(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _calculator = new PerformanceCalculator(logger);
        }

        /// <summary>
        /// Produces grade-level performance report.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Performance report.</returns>
        public GradePerformanceReport AnalyzeByGrade(List<TradeRecord> trades)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var report = new GradePerformanceReport();
                foreach (TradeGrade grade in Enum.GetValues(typeof(TradeGrade)))
                {
                    var subset = trades.Where(t => t.Grade == grade).ToList();
                    report.MetricsByGrade[grade] = _calculator.Calculate(subset);
                    report.GradeAccuracy[grade] = CalculateGradeAccuracy(grade, trades);
                }

                report.SuggestedThreshold = FindOptimalThreshold(trades);
                return report;
            }
            catch (Exception ex)
            {
                _logger.LogError("AnalyzeByGrade failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates percentage of profitable trades for a grade.
        /// </summary>
        /// <param name="grade">Target grade.</param>
        /// <param name="trades">Trade records.</param>
        /// <returns>Accuracy in range [0,1].</returns>
        public double CalculateGradeAccuracy(TradeGrade grade, List<TradeRecord> trades)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var subset = trades.Where(t => t.Grade == grade).ToList();
                if (subset.Count == 0)
                    return 0.0;

                var winners = subset.Count(t => t.RealizedPnL > 0.0);
                return (double)winners / subset.Count;
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculateGradeAccuracy failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Finds threshold with best expectancy for accepted grades and above.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Suggested threshold grade.</returns>
        public TradeGrade FindOptimalThreshold(List<TradeRecord> trades)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var ordered = new List<TradeGrade>
                {
                    TradeGrade.APlus,
                    TradeGrade.A,
                    TradeGrade.B,
                    TradeGrade.C,
                    TradeGrade.D,
                    TradeGrade.F
                };

                var bestGrade = TradeGrade.F;
                var bestExpectancy = double.MinValue;

                foreach (var threshold in ordered)
                {
                    var accepted = trades.Where(t => (int)t.Grade >= (int)threshold).ToList();
                    if (accepted.Count == 0)
                        continue;

                    var expectancy = _calculator.CalculateExpectancy(accepted);
                    if (expectancy > bestExpectancy)
                    {
                        bestExpectancy = expectancy;
                        bestGrade = threshold;
                    }
                }

                return bestGrade;
            }
            catch (Exception ex)
            {
                _logger.LogError("FindOptimalThreshold failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets metrics grouped by grade.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Metrics by grade.</returns>
        public Dictionary<TradeGrade, PerformanceMetrics> GetMetricsByGrade(List<TradeRecord> trades)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var result = new Dictionary<TradeGrade, PerformanceMetrics>();
                foreach (TradeGrade grade in Enum.GetValues(typeof(TradeGrade)))
                {
                    var subset = trades.Where(t => t.Grade == grade).ToList();
                    result.Add(grade, _calculator.Calculate(subset));
                }

                return result;
            }
            catch (Exception ex)
            {
                _logger.LogError("GetMetricsByGrade failed: {0}", ex.Message);
                throw;
            }
        }
    }
}
