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

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Strategy performance summary for portfolio optimization.
    /// </summary>
    public class StrategyPerformance
    {
        public string StrategyName { get; set; }
        public double MeanReturn { get; set; }
        public double StdDevReturn { get; set; }
        public double Sharpe { get; set; }
        public Dictionary<string, double> Correlations { get; set; }

        public StrategyPerformance()
        {
            Correlations = new Dictionary<string, double>();
        }
    }

    /// <summary>
    /// Portfolio allocation optimization result.
    /// </summary>
    public class AllocationResult
    {
        public Dictionary<string, double> Allocation { get; set; }
        public double ExpectedSharpe { get; set; }

        public AllocationResult()
        {
            Allocation = new Dictionary<string, double>();
        }
    }

    /// <summary>
    /// Optimizes allocations across multiple strategies.
    /// </summary>
    public class PortfolioOptimizer
    {
        private readonly ILogger _logger;

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

            _logger = logger;
        }

        /// <summary>
        /// Returns a Sharpe-weighted allocation.
        /// </summary>
        public AllocationResult OptimizeAllocation(List<StrategyPerformance> strategies)
        {
            if (strategies == null)
                throw new ArgumentNullException("strategies");

            try
            {
                var result = new AllocationResult();
                if (strategies.Count == 0)
                    return result;

                var positive = strategies.Select(s => new
                {
                    Name = s.StrategyName,
                    Score = Math.Max(0.0001, s.Sharpe)
                }).ToList();

                var total = positive.Sum(s => s.Score);
                foreach (var s in positive)
                {
                    result.Allocation[s.Name] = s.Score / total;
                }

                result.ExpectedSharpe = CalculatePortfolioSharpe(result.Allocation, strategies);
                return result;
            }
            catch (Exception ex)
            {
                _logger.LogError("OptimizeAllocation failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Computes approximate portfolio Sharpe.
        /// </summary>
        public double CalculatePortfolioSharpe(Dictionary<string, double> allocation, List<StrategyPerformance> strategies)
        {
            if (allocation == null)
                throw new ArgumentNullException("allocation");
            if (strategies == null)
                throw new ArgumentNullException("strategies");

            try
            {
                if (allocation.Count == 0 || strategies.Count == 0)
                    return 0.0;

                var byName = strategies.ToDictionary(s => s.StrategyName, s => s);

                var mean = 0.0;
                foreach (var kv in allocation)
                {
                    if (byName.ContainsKey(kv.Key))
                        mean += kv.Value * byName[kv.Key].MeanReturn;
                }

                var variance = 0.0;
                foreach (var i in allocation)
                {
                    if (!byName.ContainsKey(i.Key))
                        continue;

                    var si = byName[i.Key];
                    foreach (var j in allocation)
                    {
                        if (!byName.ContainsKey(j.Key))
                            continue;

                        var sj = byName[j.Key];
                        var corr = 0.0;
                        if (i.Key == j.Key)
                        {
                            corr = 1.0;
                        }
                        else if (si.Correlations.ContainsKey(j.Key))
                        {
                            corr = si.Correlations[j.Key];
                        }
                        else if (sj.Correlations.ContainsKey(i.Key))
                        {
                            corr = sj.Correlations[i.Key];
                        }

                        variance += i.Value * j.Value * si.StdDevReturn * sj.StdDevReturn * corr;
                    }
                }

                var std = variance > 0.0 ? Math.Sqrt(variance) : 0.0;
                if (std <= 0.0)
                    return 0.0;

                return mean / std;
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculatePortfolioSharpe failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Computes inverse-volatility risk parity allocation.
        /// </summary>
        public Dictionary<string, double> RiskParityAllocation(List<StrategyPerformance> strategies)
        {
            if (strategies == null)
                throw new ArgumentNullException("strategies");

            try
            {
                var result = new Dictionary<string, double>();
                if (strategies.Count == 0)
                    return result;

                var invVol = new Dictionary<string, double>();
                foreach (var s in strategies)
                {
                    var vol = s.StdDevReturn > 0.000001 ? s.StdDevReturn : 0.000001;
                    invVol[s.StrategyName] = 1.0 / vol;
                }

                var total = invVol.Sum(v => v.Value);
                foreach (var kv in invVol)
                {
                    result[kv.Key] = kv.Value / total;
                }

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