using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using NT8.Core.Common.Models;
using NT8.Core.Logging;

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Result for single-parameter optimization.
    /// </summary>
    public class OptimizationResult
    {
        public string ParameterName { get; set; }
        public Dictionary<double, PerformanceMetrics> MetricsByValue { get; set; }
        public double OptimalValue { get; set; }

        public OptimizationResult()
        {
            MetricsByValue = new Dictionary<double, PerformanceMetrics>();
        }
    }

    /// <summary>
    /// Result for multi-parameter grid search.
    /// </summary>
    public class GridSearchResult
    {
        public Dictionary<string, PerformanceMetrics> MetricsByCombination { get; set; }
        public Dictionary<string, double> BestParameters { get; set; }

        public GridSearchResult()
        {
            MetricsByCombination = new Dictionary<string, PerformanceMetrics>();
            BestParameters = new Dictionary<string, double>();
        }
    }

    /// <summary>
    /// Walk-forward optimization result.
    /// </summary>
    public class WalkForwardResult
    {
        public PerformanceMetrics InSampleMetrics { get; set; }
        public PerformanceMetrics OutOfSampleMetrics { get; set; }
        public double StabilityScore { get; set; }

        public WalkForwardResult()
        {
            InSampleMetrics = new PerformanceMetrics();
            OutOfSampleMetrics = new PerformanceMetrics();
        }
    }

    /// <summary>
    /// Parameter optimization utility.
    /// </summary>
    public class ParameterOptimizer
    {
        private readonly ILogger _logger;
        private readonly PerformanceCalculator _calculator;

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

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

        /// <summary>
        /// Optimizes one parameter by replaying filtered trade subsets.
        /// </summary>
        public OptimizationResult OptimizeParameter(string paramName, List<double> values, List<TradeRecord> trades)
        {
            if (string.IsNullOrEmpty(paramName))
                throw new ArgumentNullException("paramName");
            if (values == null)
                throw new ArgumentNullException("values");
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var result = new OptimizationResult();
                result.ParameterName = paramName;

                var bestScore = double.MinValue;
                var bestValue = values.Count > 0 ? values[0] : 0.0;

                foreach (var value in values)
                {
                    var sample = BuildSyntheticSubset(paramName, value, trades);
                    var metrics = _calculator.Calculate(sample);
                    result.MetricsByValue[value] = metrics;

                    var score = metrics.Expectancy;
                    if (score > bestScore)
                    {
                        bestScore = score;
                        bestValue = value;
                    }
                }

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

        /// <summary>
        /// Runs a grid search for multiple parameters.
        /// </summary>
        public GridSearchResult GridSearch(Dictionary<string, List<double>> parameters, List<TradeRecord> trades)
        {
            if (parameters == null)
                throw new ArgumentNullException("parameters");
            if (trades == null)
                throw new ArgumentNullException("trades");

            try
            {
                var result = new GridSearchResult();
                var keys = parameters.Keys.ToList();
                if (keys.Count == 0)
                    return result;

                var combos = BuildCombinations(parameters, keys, 0, new Dictionary<string, double>());
                var bestScore = double.MinValue;
                Dictionary<string, double> best = null;

                foreach (var combo in combos)
                {
                    var sample = trades;
                    foreach (var kv in combo)
                    {
                        sample = BuildSyntheticSubset(kv.Key, kv.Value, sample);
                    }

                    var metrics = _calculator.Calculate(sample);
                    var key = SerializeCombo(combo);
                    result.MetricsByCombination[key] = metrics;

                    if (metrics.Expectancy > bestScore)
                    {
                        bestScore = metrics.Expectancy;
                        best = new Dictionary<string, double>(combo);
                    }
                }

                if (best != null)
                    result.BestParameters = best;

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

        /// <summary>
        /// Performs basic walk-forward validation.
        /// </summary>
        public WalkForwardResult WalkForwardTest(StrategyConfig config, List<BarData> historicalData)
        {
            if (config == null)
                throw new ArgumentNullException("config");
            if (historicalData == null)
                throw new ArgumentNullException("historicalData");

            try
            {
                var mid = historicalData.Count / 2;
                var inSampleBars = historicalData.Take(mid).ToList();
                var outSampleBars = historicalData.Skip(mid).ToList();

                var inTrades = BuildPseudoTradesFromBars(inSampleBars, config.Symbol);
                var outTrades = BuildPseudoTradesFromBars(outSampleBars, config.Symbol);

                var result = new WalkForwardResult();
                result.InSampleMetrics = _calculator.Calculate(inTrades);
                result.OutOfSampleMetrics = _calculator.Calculate(outTrades);

                var inExp = result.InSampleMetrics.Expectancy;
                var outExp = result.OutOfSampleMetrics.Expectancy;
                var denominator = Math.Abs(inExp) > 0.000001 ? Math.Abs(inExp) : 1.0;
                var drift = Math.Abs(inExp - outExp) / denominator;
                result.StabilityScore = Math.Max(0.0, 1.0 - drift);

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

        private static List<TradeRecord> BuildSyntheticSubset(string paramName, double value, List<TradeRecord> trades)
        {
            if (trades.Count == 0)
                return new List<TradeRecord>();

            var percentile = Math.Max(0.05, Math.Min(0.95, value / (Math.Abs(value) + 1.0)));
            var take = Math.Max(1, (int)Math.Round(trades.Count * percentile));
            return trades
                .OrderByDescending(t => t.ConfluenceScore)
                .Take(take)
                .Select(Clone)
                .ToList();
        }

        private static List<Dictionary<string, double>> BuildCombinations(
            Dictionary<string, List<double>> parameters,
            List<string> keys,
            int index,
            Dictionary<string, double> current)
        {
            var results = new List<Dictionary<string, double>>();
            if (index >= keys.Count)
            {
                results.Add(new Dictionary<string, double>(current));
                return results;
            }

            var key = keys[index];
            foreach (var value in parameters[key])
            {
                current[key] = value;
                results.AddRange(BuildCombinations(parameters, keys, index + 1, current));
            }

            return results;
        }

        private static string SerializeCombo(Dictionary<string, double> combo)
        {
            return string.Join(";", combo.OrderBy(k => k.Key).Select(k => string.Format(CultureInfo.InvariantCulture, "{0}={1}", k.Key, k.Value)).ToArray());
        }

        private static List<TradeRecord> BuildPseudoTradesFromBars(List<BarData> bars, string symbol)
        {
            var trades = new List<TradeRecord>();
            for (var i = 1; i < bars.Count; i++)
            {
                var prev = bars[i - 1];
                var curr = bars[i];

                var trade = new TradeRecord();
                trade.TradeId = string.Format("WF-{0}", i);
                trade.Symbol = symbol;
                trade.StrategyName = "WalkForward";
                trade.EntryTime = prev.Time;
                trade.ExitTime = curr.Time;
                trade.Side = curr.Close >= prev.Close ? Common.Models.OrderSide.Buy : Common.Models.OrderSide.Sell;
                trade.Quantity = 1;
                trade.EntryPrice = prev.Close;
                trade.ExitPrice = curr.Close;
                trade.RealizedPnL = curr.Close - prev.Close;
                trade.UnrealizedPnL = 0.0;
                trade.Grade = trade.RealizedPnL >= 0.0 ? Intelligence.TradeGrade.B : Intelligence.TradeGrade.D;
                trade.ConfluenceScore = 0.6;
                trade.RiskMode = Intelligence.RiskMode.PCP;
                trade.VolatilityRegime = Intelligence.VolatilityRegime.Normal;
                trade.TrendRegime = Intelligence.TrendRegime.Range;
                trade.StopTicks = 8;
                trade.TargetTicks = 16;
                trade.RMultiple = trade.RealizedPnL / 8.0;
                trade.Duration = trade.ExitTime.Value - trade.EntryTime;
                trades.Add(trade);
            }

            return trades;
        }

        private static TradeRecord Clone(TradeRecord input)
        {
            var copy = new TradeRecord();
            copy.TradeId = input.TradeId;
            copy.Symbol = input.Symbol;
            copy.StrategyName = input.StrategyName;
            copy.EntryTime = input.EntryTime;
            copy.ExitTime = input.ExitTime;
            copy.Side = input.Side;
            copy.Quantity = input.Quantity;
            copy.EntryPrice = input.EntryPrice;
            copy.ExitPrice = input.ExitPrice;
            copy.RealizedPnL = input.RealizedPnL;
            copy.UnrealizedPnL = input.UnrealizedPnL;
            copy.Grade = input.Grade;
            copy.ConfluenceScore = input.ConfluenceScore;
            copy.RiskMode = input.RiskMode;
            copy.VolatilityRegime = input.VolatilityRegime;
            copy.TrendRegime = input.TrendRegime;
            copy.StopTicks = input.StopTicks;
            copy.TargetTicks = input.TargetTicks;
            copy.RMultiple = input.RMultiple;
            copy.Duration = input.Duration;
            copy.Metadata = new Dictionary<string, object>(input.Metadata);
            return copy;
        }
    }
}
