// File: OptimalFCalculator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using NT8.Core.Logging;

namespace NT8.Core.Sizing
{
    /// <summary>
    /// Implements Ralph Vince's Optimal-f position sizing algorithm.
    /// Calculates the fraction of capital that maximizes geometric growth
    /// based on historical trade results.
    /// </summary>
    public class OptimalFCalculator
    {
        private readonly ILogger _logger;
        private readonly object _lock;

        /// <summary>
        /// Default number of iterations for optimization search
        /// </summary>
        public const int DEFAULT_ITERATIONS = 100;

        /// <summary>
        /// Default step size for optimization search
        /// </summary>
        public const double DEFAULT_STEP_SIZE = 0.01;

        /// <summary>
        /// Minimum allowable f value
        /// </summary>
        public const double MIN_F = 0.01;

        /// <summary>
        /// Maximum allowable f value
        /// </summary>
        public const double MAX_F = 1.0;

        /// <summary>
        /// Minimum number of trades required for calculation
        /// </summary>
        public const int MIN_TRADES_REQUIRED = 10;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="logger">Logger instance</param>
        /// <exception cref="ArgumentNullException">Logger is null</exception>
        public OptimalFCalculator(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _lock = new object();

            _logger.LogDebug("OptimalFCalculator initialized");
        }

        /// <summary>
        /// Calculate optimal-f using Ralph Vince's method
        /// </summary>
        /// <param name="input">Optimal-f calculation input</param>
        /// <returns>Optimal-f calculation result</returns>
        /// <exception cref="ArgumentNullException">Input is null</exception>
        /// <exception cref="ArgumentException">Invalid input parameters</exception>
        public OptimalFResult Calculate(OptimalFInput input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            try
            {
                // Validate input
                ValidateInput(input);

                _logger.LogDebug("Calculating optimal-f for {0} trades", input.TradeResults.Count);

                // Find largest loss (denominator for f calculation)
                double largestLoss = FindLargestLoss(input.TradeResults);

                if (Math.Abs(largestLoss) < 0.01)
                {
                    _logger.LogWarning("No significant losses in trade history - using conservative f=0.1");
                    return new OptimalFResult(
                        optimalF: 0.1,
                        contracts: 0,
                        expectedGrowth: 1.0,
                        largestLoss: 0.0,
                        tradeCount: input.TradeResults.Count,
                        confidence: 0.5,
                        isValid: true,
                        validationMessage: "Conservative fallback - no significant losses"
                    );
                }

                // Search for optimal f value
                double optimalF = SearchOptimalF(
                    input.TradeResults,
                    largestLoss,
                    input.MaxFLimit,
                    input.StepSize
                );

                // Calculate performance metrics at optimal f
                double twr = CalculateTWR(input.TradeResults, optimalF, largestLoss);
                double geometricMean = CalculateGeometricMean(twr, input.TradeResults.Count);

                // Calculate confidence score based on trade sample size and consistency
                double confidenceScore = CalculateConfidenceScore(
                    input.TradeResults,
                    optimalF,
                    largestLoss
                );

                // Apply safety factor if requested
                double adjustedF = optimalF;
                if (input.SafetyFactor < 1.0)
                {
                    adjustedF = optimalF * input.SafetyFactor;
                    _logger.LogDebug("Applied safety factor {0:F2} to optimal-f: {1:F3} -> {2:F3}",
                        input.SafetyFactor, optimalF, adjustedF);
                }

                var result = new OptimalFResult(
                    optimalF: adjustedF,
                    contracts: 0,
                    expectedGrowth: geometricMean,
                    largestLoss: -largestLoss,
                    tradeCount: input.TradeResults.Count,
                    confidence: confidenceScore,
                    isValid: true,
                    validationMessage: String.Empty
                );

                _logger.LogInformation(
                    "Optimal-f calculated: f={0:F3}, TWR={1:F3}, GM={2:F3}, confidence={3:F2}",
                    result.OptimalF, twr, result.ExpectedGrowth, result.Confidence
                );

                return result;
            }
            catch (ArgumentException)
            {
                throw;
            }
            catch (Exception ex)
            {
                _logger.LogError("Optimal-f calculation failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Validate input parameters
        /// </summary>
        private void ValidateInput(OptimalFInput input)
        {
            if (input.TradeResults == null)
                throw new ArgumentException("Trade results cannot be null");

            if (input.TradeResults.Count < MIN_TRADES_REQUIRED)
            {
                throw new ArgumentException(
                    String.Format("Minimum {0} trades required, got {1}",
                        MIN_TRADES_REQUIRED, input.TradeResults.Count)
                );
            }

            if (input.MaxFLimit <= 0 || input.MaxFLimit > MAX_F)
            {
                throw new ArgumentException(
                    String.Format("MaxFLimit must be between 0 and {0}", MAX_F)
                );
            }

            if (input.StepSize <= 0 || input.StepSize > 0.1)
                throw new ArgumentException("StepSize must be between 0 and 0.1");

            if (input.SafetyFactor <= 0 || input.SafetyFactor > 1.0)
                throw new ArgumentException("SafetyFactor must be between 0 and 1.0");

            // Check for all-zero trades
            if (input.TradeResults.All(t => Math.Abs(t) < 0.01))
                throw new ArgumentException("Trade results contain no significant P&L");
        }

        /// <summary>
        /// Find the largest loss in trade results (absolute value)
        /// </summary>
        private double FindLargestLoss(List<double> tradeResults)
        {
            double largestLoss = 0;

            foreach (var result in tradeResults)
            {
                if (result < 0 && Math.Abs(result) > Math.Abs(largestLoss))
                {
                    largestLoss = result;
                }
            }

            return Math.Abs(largestLoss);
        }

        /// <summary>
        /// Search for optimal f value using grid search with refinement
        /// </summary>
        private double SearchOptimalF(
            List<double> tradeResults,
            double largestLoss,
            double maxF,
            double stepSize)
        {
            double bestF = MIN_F;
            double bestTWR = 0;

            // Coarse search
            for (double f = MIN_F; f <= maxF; f += stepSize)
            {
                double twr = CalculateTWR(tradeResults, f, largestLoss);

                if (twr > bestTWR)
                {
                    bestTWR = twr;
                    bestF = f;
                }
            }

            // Fine-tune search around best f
            double fineStep = stepSize / 10.0;
            double searchStart = Math.Max(MIN_F, bestF - stepSize);
            double searchEnd = Math.Min(maxF, bestF + stepSize);

            for (double f = searchStart; f <= searchEnd; f += fineStep)
            {
                double twr = CalculateTWR(tradeResults, f, largestLoss);

                if (twr > bestTWR)
                {
                    bestTWR = twr;
                    bestF = f;
                }
            }

            _logger.LogDebug("Optimal-f search: best f={0:F3} with TWR={1:F3}", bestF, bestTWR);

            return bestF;
        }

        /// <summary>
        /// Calculate Terminal Wealth Relative (TWR) for given f value
        /// TWR = product of (1 + (trade_i / largest_loss) * f) for all trades
        /// </summary>
        private double CalculateTWR(List<double> tradeResults, double f, double largestLoss)
        {
            if (Math.Abs(largestLoss) < 0.01)
                return 1.0;

            double twr = 1.0;

            foreach (var trade in tradeResults)
            {
                // HPR = 1 + (trade / largest_loss) * f
                double hpr = 1.0 + (trade / largestLoss) * f;

                // Prevent negative or zero TWR (ruins)
                if (hpr <= 0)
                {
                    return 0.0;
                }

                twr *= hpr;

                // Check for overflow
                if (Double.IsInfinity(twr) || Double.IsNaN(twr))
                {
                    return 0.0;
                }
            }

            return twr;
        }

        /// <summary>
        /// Calculate geometric mean from TWR and number of trades
        /// GM = TWR^(1/n)
        /// </summary>
        private double CalculateGeometricMean(double twr, int tradeCount)
        {
            if (twr <= 0 || tradeCount <= 0)
                return 0.0;

            try
            {
                double gm = Math.Pow(twr, 1.0 / tradeCount);
                return gm;
            }
            catch (Exception)
            {
                return 0.0;
            }
        }

        /// <summary>
        /// Calculate confidence score for optimal-f result
        /// Based on sample size, win rate consistency, and drawdown severity
        /// </summary>
        private double CalculateConfidenceScore(
            List<double> tradeResults,
            double optimalF,
            double largestLoss)
        {
            // Factor 1: Sample size (more trades = higher confidence)
            double sampleScore = Math.Min(1.0, tradeResults.Count / 100.0);

            // Factor 2: Win rate consistency
            int winners = tradeResults.Count(t => t > 0);
            double winRate = (double)winners / tradeResults.Count;
            double winRateScore = 1.0 - Math.Abs(winRate - 0.5); // Closer to 50% is more stable

            // Factor 3: Optimal f reasonableness (too high f is risky)
            double fScore = 1.0 - (optimalF / MAX_F);

            // Factor 4: Loss distribution (concentrated losses = lower confidence)
            double avgLoss = CalculateAverageLoss(tradeResults);
            double lossConcentration = avgLoss > 0 ? largestLoss / avgLoss : 1.0;
            double distributionScore = Math.Max(0, 1.0 - (lossConcentration / 5.0));

            // Weighted average
            double confidence =
                (sampleScore * 0.3) +
                (winRateScore * 0.2) +
                (fScore * 0.3) +
                (distributionScore * 0.2);

            return Math.Max(0.0, Math.Min(1.0, confidence));
        }

        /// <summary>
        /// Calculate average loss from trade results
        /// </summary>
        private double CalculateAverageLoss(List<double> tradeResults)
        {
            var losses = tradeResults.Where(t => t < 0).ToList();

            if (losses.Count == 0)
                return 0.0;

            double sum = 0;
            foreach (var loss in losses)
            {
                sum += Math.Abs(loss);
            }

            return sum / losses.Count;
        }

        /// <summary>
        /// Calculate Kelly fraction (simplified formula for comparison)
        /// Kelly = (WinRate * AvgWin - LossRate * AvgLoss) / AvgWin
        /// </summary>
        /// <param name="tradeResults">Historical trade results</param>
        /// <returns>Kelly fraction</returns>
        public double CalculateKellyFraction(List<double> tradeResults)
        {
            if (tradeResults == null || tradeResults.Count < MIN_TRADES_REQUIRED)
                throw new ArgumentException("Insufficient trade history for Kelly calculation");

            try
            {
                var winners = tradeResults.Where(t => t > 0).ToList();
                var losers = tradeResults.Where(t => t < 0).ToList();

                if (winners.Count == 0 || losers.Count == 0)
                {
                    _logger.LogWarning("Kelly calculation: no winners or no losers in history");
                    return 0.1;
                }

                double winRate = (double)winners.Count / tradeResults.Count;
                double lossRate = 1.0 - winRate;

                double avgWin = winners.Average();
                double avgLoss = Math.Abs(losers.Average());

                if (avgWin < 0.01)
                {
                    _logger.LogWarning("Kelly calculation: average win too small");
                    return 0.1;
                }

                double kelly = (winRate * avgWin - lossRate * avgLoss) / avgWin;

                // Kelly can be negative (negative edge) or > 1 (dangerous)
                kelly = Math.Max(0.01, Math.Min(0.5, kelly));

                _logger.LogDebug("Kelly fraction calculated: {0:F3}", kelly);

                return kelly;
            }
            catch (Exception ex)
            {
                _logger.LogError("Kelly calculation failed: {0}", ex.Message);
                return 0.1; // Conservative fallback
            }
        }

        /// <summary>
        /// Generate performance curve showing TWR at different f values
        /// Useful for visualizing optimal-f and understanding sensitivity
        /// </summary>
        /// <param name="tradeResults">Historical trade results</param>
        /// <param name="stepSize">Step size for f values</param>
        /// <returns>Dictionary of f values to TWR</returns>
        public Dictionary<double, double> GeneratePerformanceCurve(
            List<double> tradeResults,
            double stepSize)
        {
            if (tradeResults == null || tradeResults.Count < MIN_TRADES_REQUIRED)
                throw new ArgumentException("Insufficient trade history");

            if (stepSize <= 0 || stepSize > 0.1)
                throw new ArgumentException("Invalid step size");

            var curve = new Dictionary<double, double>();
            double largestLoss = FindLargestLoss(tradeResults);

            if (Math.Abs(largestLoss) < 0.01)
            {
                _logger.LogWarning("No significant losses - performance curve will be flat");
                return curve;
            }

            try
            {
                for (double f = MIN_F; f <= MAX_F; f += stepSize)
                {
                    double twr = CalculateTWR(tradeResults, f, largestLoss);
                    curve.Add(Math.Round(f, 3), twr);
                }

                _logger.LogDebug("Generated performance curve with {0} points", curve.Count);

                return curve;
            }
            catch (Exception ex)
            {
                _logger.LogError("Performance curve generation failed: {0}", ex.Message);
                throw;
            }
        }
    }
}
