using System;
using System.Collections.Generic;
using NT8.Core.Common.Models;

namespace NT8.Core.Sizing
{
    /// <summary>
    /// Represents input parameters for Optimal-f calculation
    /// </summary>
    public class OptimalFInput
    {
        /// <summary>
        /// Historical trade results (positive for wins, negative for losses)
        /// </summary>
        public List<double> TradeResults { get; private set; }

        /// <summary>
        /// Maximum f value to consider (default 1.0)
        /// </summary>
        public double MaxFLimit { get; private set; }

        /// <summary>
        /// Step size for optimization search (default 0.01)
        /// </summary>
        public double StepSize { get; private set; }

        /// <summary>
        /// Safety factor to apply to optimal-f (default 1.0, use 0.5 for half-Kelly)
        /// </summary>
        public double SafetyFactor { get; private set; }

        /// <summary>
        /// Initializes a new instance of the OptimalFInput class
        /// </summary>
        /// <param name="tradeResults">Historical trade results</param>
        /// <param name="maxFLimit">Maximum f value to consider</param>
        /// <param name="stepSize">Step size for search</param>
        /// <param name="safetyFactor">Safety factor to apply</param>
        public OptimalFInput(
            List<double> tradeResults,
            double maxFLimit,
            double stepSize,
            double safetyFactor)
        {
            if (tradeResults == null)
                throw new ArgumentNullException("tradeResults");
            if (maxFLimit <= 0.0 || maxFLimit > 1.0)
                throw new ArgumentOutOfRangeException("maxFLimit", "MaxFLimit must be between 0 and 1");
            if (stepSize <= 0.0)
                throw new ArgumentOutOfRangeException("stepSize", "StepSize must be positive");
            if (safetyFactor <= 0.0 || safetyFactor > 1.0)
                throw new ArgumentOutOfRangeException("safetyFactor", "SafetyFactor must be between 0 and 1");

            TradeResults = tradeResults;
            MaxFLimit = maxFLimit;
            StepSize = stepSize;
            SafetyFactor = safetyFactor;
        }

        /// <summary>
        /// Creates default input with standard parameters
        /// </summary>
        /// <param name="tradeResults">Historical trade results</param>
        /// <returns>OptimalFInput with default parameters</returns>
        public static OptimalFInput CreateDefault(List<double> tradeResults)
        {
            if (tradeResults == null)
                throw new ArgumentNullException("tradeResults");

            return new OptimalFInput(
                tradeResults: tradeResults,
                maxFLimit: 1.0,
                stepSize: 0.01,
                safetyFactor: 1.0);
        }
    }

    /// <summary>
    /// Represents the result of an Optimal-f calculation (Ralph Vince method)
    /// </summary>
    public class OptimalFResult
    {
        /// <summary>
        /// Optimal-f value (fraction of capital to risk per trade)
        /// </summary>
        public double OptimalF { get; private set; }

        /// <summary>
        /// Number of contracts calculated from Optimal-f
        /// </summary>
        public int Contracts { get; private set; }

        /// <summary>
        /// Expected growth rate with this position size (Geometric Mean)
        /// </summary>
        public double ExpectedGrowth { get; private set; }

        /// <summary>
        /// Largest historical loss used in calculation (absolute value)
        /// </summary>
        public double LargestLoss { get; private set; }

        /// <summary>
        /// Number of historical trades analyzed
        /// </summary>
        public int TradeCount { get; private set; }

        /// <summary>
        /// Confidence level of the calculation (0 to 1)
        /// </summary>
        public double Confidence { get; private set; }

        /// <summary>
        /// Whether the result is valid and usable
        /// </summary>
        public bool IsValid { get; private set; }

        /// <summary>
        /// Validation message if result is invalid
        /// </summary>
        public string ValidationMessage { get; private set; }

        /// <summary>
        /// Initializes a new instance of the OptimalFResult class
        /// </summary>
        /// <param name="optimalF">Optimal-f value (0 to 1)</param>
        /// <param name="contracts">Number of contracts</param>
        /// <param name="expectedGrowth">Expected growth rate (geometric mean)</param>
        /// <param name="largestLoss">Largest historical loss</param>
        /// <param name="tradeCount">Number of trades analyzed</param>
        /// <param name="confidence">Confidence level (0 to 1)</param>
        /// <param name="isValid">Whether result is valid</param>
        /// <param name="validationMessage">Validation message</param>
        public OptimalFResult(
            double optimalF,
            int contracts,
            double expectedGrowth,
            double largestLoss,
            int tradeCount,
            double confidence,
            bool isValid,
            string validationMessage)
        {
            if (optimalF < 0.0 || optimalF > 1.0)
                throw new ArgumentOutOfRangeException("optimalF", "Optimal-f must be between 0 and 1");
            if (contracts < 0)
                throw new ArgumentOutOfRangeException("contracts", "Contracts cannot be negative");
            if (largestLoss > 0)
                throw new ArgumentException("Largest loss must be negative or zero", "largestLoss");
            if (tradeCount < 0)
                throw new ArgumentOutOfRangeException("tradeCount", "Trade count cannot be negative");
            if (confidence < 0.0 || confidence > 1.0)
                throw new ArgumentOutOfRangeException("confidence", "Confidence must be between 0 and 1");

            OptimalF = optimalF;
            Contracts = contracts;
            ExpectedGrowth = expectedGrowth;
            LargestLoss = largestLoss;
            TradeCount = tradeCount;
            Confidence = confidence;
            IsValid = isValid;
            ValidationMessage = validationMessage ?? string.Empty;
        }

        /// <summary>
        /// Creates an invalid result with an error message
        /// </summary>
        /// <param name="validationMessage">Reason for invalidity</param>
        /// <returns>Invalid OptimalFResult</returns>
        public static OptimalFResult CreateInvalid(string validationMessage)
        {
            if (string.IsNullOrEmpty(validationMessage))
                throw new ArgumentNullException("validationMessage");

            return new OptimalFResult(
                optimalF: 0.0,
                contracts: 0,
                expectedGrowth: 0.0,
                largestLoss: 0.0,
                tradeCount: 0,
                confidence: 0.0,
                isValid: false,
                validationMessage: validationMessage);
        }
    }

    /// <summary>
    /// Represents volatility metrics used for position sizing
    /// </summary>
    public class VolatilityMetrics
    {
        /// <summary>
        /// Average True Range (ATR) value
        /// </summary>
        public double ATR { get; private set; }

        /// <summary>
        /// Standard deviation of returns
        /// </summary>
        public double StandardDeviation { get; private set; }

        /// <summary>
        /// Current volatility regime classification
        /// </summary>
        public VolatilityRegime Regime { get; private set; }

        /// <summary>
        /// Historical volatility (annualized)
        /// </summary>
        public double HistoricalVolatility { get; private set; }

        /// <summary>
        /// Percentile rank of current volatility (0 to 100)
        /// </summary>
        public double VolatilityPercentile { get; private set; }

        /// <summary>
        /// Number of periods used in calculation
        /// </summary>
        public int Periods { get; private set; }

        /// <summary>
        /// Timestamp of the calculation
        /// </summary>
        public DateTime Timestamp { get; private set; }

        /// <summary>
        /// Whether the metrics are valid and current
        /// </summary>
        public bool IsValid { get; private set; }

        /// <summary>
        /// Initializes a new instance of the VolatilityMetrics class
        /// </summary>
        /// <param name="atr">Average True Range value</param>
        /// <param name="standardDeviation">Standard deviation of returns</param>
        /// <param name="regime">Volatility regime</param>
        /// <param name="historicalVolatility">Historical volatility (annualized)</param>
        /// <param name="volatilityPercentile">Percentile rank (0 to 100)</param>
        /// <param name="periods">Number of periods used</param>
        /// <param name="timestamp">Calculation timestamp</param>
        /// <param name="isValid">Whether metrics are valid</param>
        public VolatilityMetrics(
            double atr,
            double standardDeviation,
            VolatilityRegime regime,
            double historicalVolatility,
            double volatilityPercentile,
            int periods,
            DateTime timestamp,
            bool isValid)
        {
            if (atr < 0.0)
                throw new ArgumentOutOfRangeException("atr", "ATR cannot be negative");
            if (standardDeviation < 0.0)
                throw new ArgumentOutOfRangeException("standardDeviation", "Standard deviation cannot be negative");
            if (historicalVolatility < 0.0)
                throw new ArgumentOutOfRangeException("historicalVolatility", "Historical volatility cannot be negative");
            if (volatilityPercentile < 0.0 || volatilityPercentile > 100.0)
                throw new ArgumentOutOfRangeException("volatilityPercentile", "Percentile must be between 0 and 100");
            if (periods <= 0)
                throw new ArgumentOutOfRangeException("periods", "Periods must be positive");

            ATR = atr;
            StandardDeviation = standardDeviation;
            Regime = regime;
            HistoricalVolatility = historicalVolatility;
            VolatilityPercentile = volatilityPercentile;
            Periods = periods;
            Timestamp = timestamp;
            IsValid = isValid;
        }

        /// <summary>
        /// Creates invalid volatility metrics
        /// </summary>
        /// <returns>Invalid VolatilityMetrics</returns>
        public static VolatilityMetrics CreateInvalid()
        {
            return new VolatilityMetrics(
                atr: 0.0,
                standardDeviation: 0.0,
                regime: VolatilityRegime.Unknown,
                historicalVolatility: 0.0,
                volatilityPercentile: 0.0,
                periods: 1,
                timestamp: DateTime.UtcNow,
                isValid: false);
        }
    }

    /// <summary>
    /// Defines volatility regime classifications
    /// </summary>
    public enum VolatilityRegime
    {
        /// <summary>
        /// Volatility regime is unknown or undefined
        /// </summary>
        Unknown = 0,

        /// <summary>
        /// Very low volatility (0-20th percentile)
        /// </summary>
        VeryLow = 1,

        /// <summary>
        /// Low volatility (20-40th percentile)
        /// </summary>
        Low = 2,

        /// <summary>
        /// Normal volatility (40-60th percentile)
        /// </summary>
        Normal = 3,

        /// <summary>
        /// High volatility (60-80th percentile)
        /// </summary>
        High = 4,

        /// <summary>
        /// Very high volatility (80-100th percentile)
        /// </summary>
        VeryHigh = 5,

        /// <summary>
        /// Extreme volatility (above historical ranges)
        /// </summary>
        Extreme = 6
    }

    /// <summary>
    /// Defines rounding modes for contract calculations
    /// </summary>
    public enum RoundingMode
    {
        /// <summary>
        /// Round down to nearest integer (conservative)
        /// </summary>
        Floor = 0,

        /// <summary>
        /// Round up to nearest integer (aggressive)
        /// </summary>
        Ceiling = 1,

        /// <summary>
        /// Round to nearest integer (standard rounding)
        /// </summary>
        Nearest = 2
    }

    /// <summary>
    /// Represents constraints on contract quantities
    /// </summary>
    public class ContractConstraints
    {
        /// <summary>
        /// Minimum number of contracts allowed
        /// </summary>
        public int MinContracts { get; private set; }

        /// <summary>
        /// Maximum number of contracts allowed
        /// </summary>
        public int MaxContracts { get; private set; }

        /// <summary>
        /// Lot size (contracts must be multiples of this)
        /// </summary>
        public int LotSize { get; private set; }

        /// <summary>
        /// Rounding mode for fractional contracts
        /// </summary>
        public RoundingMode RoundingMode { get; private set; }

        /// <summary>
        /// Whether to enforce strict lot size multiples
        /// </summary>
        public bool EnforceLotSize { get; private set; }

        /// <summary>
        /// Maximum position value in dollars (optional)
        /// </summary>
        public double? MaxPositionValue { get; private set; }

        /// <summary>
        /// Initializes a new instance of the ContractConstraints class
        /// </summary>
        /// <param name="minContracts">Minimum contracts (must be positive)</param>
        /// <param name="maxContracts">Maximum contracts (must be >= minContracts)</param>
        /// <param name="lotSize">Lot size (must be positive)</param>
        /// <param name="roundingMode">Rounding mode for fractional contracts</param>
        /// <param name="enforceLotSize">Whether to enforce lot size multiples</param>
        /// <param name="maxPositionValue">Maximum position value in dollars</param>
        public ContractConstraints(
            int minContracts,
            int maxContracts,
            int lotSize,
            RoundingMode roundingMode,
            bool enforceLotSize,
            double? maxPositionValue)
        {
            if (minContracts < 0)
                throw new ArgumentOutOfRangeException("minContracts", "Minimum contracts cannot be negative");
            if (maxContracts < minContracts)
                throw new ArgumentException("Maximum contracts must be >= minimum contracts", "maxContracts");
            if (lotSize <= 0)
                throw new ArgumentOutOfRangeException("lotSize", "Lot size must be positive");
            if (maxPositionValue.HasValue && maxPositionValue.Value <= 0.0)
                throw new ArgumentOutOfRangeException("maxPositionValue", "Max position value must be positive");

            MinContracts = minContracts;
            MaxContracts = maxContracts;
            LotSize = lotSize;
            RoundingMode = roundingMode;
            EnforceLotSize = enforceLotSize;
            MaxPositionValue = maxPositionValue;
        }

        /// <summary>
        /// Creates default constraints (1-100 contracts, lot size 1, round down)
        /// </summary>
        /// <returns>Default ContractConstraints</returns>
        public static ContractConstraints CreateDefault()
        {
            return new ContractConstraints(
                minContracts: 1,
                maxContracts: 100,
                lotSize: 1,
                roundingMode: RoundingMode.Floor,
                enforceLotSize: false,
                maxPositionValue: null);
        }

        /// <summary>
        /// Applies constraints to a calculated contract quantity
        /// </summary>
        /// <param name="calculatedContracts">Raw calculated contracts</param>
        /// <returns>Constrained contract quantity</returns>
        public int ApplyConstraints(double calculatedContracts)
        {
            if (calculatedContracts < 0.0)
                throw new ArgumentException("Calculated contracts cannot be negative", "calculatedContracts");

            // Apply rounding mode
            int rounded;
            switch (RoundingMode)
            {
                case RoundingMode.Floor:
                    rounded = (int)Math.Floor(calculatedContracts);
                    break;
                case RoundingMode.Ceiling:
                    rounded = (int)Math.Ceiling(calculatedContracts);
                    break;
                case RoundingMode.Nearest:
                    rounded = (int)Math.Round(calculatedContracts);
                    break;
                default:
                    rounded = (int)Math.Floor(calculatedContracts);
                    break;
            }

            // Enforce lot size if required
            if (EnforceLotSize && LotSize > 1)
            {
                rounded = (rounded / LotSize) * LotSize;
            }

            // Apply min/max constraints
            if (rounded < MinContracts)
                return MinContracts;
            if (rounded > MaxContracts)
                return MaxContracts;

            return rounded;
        }

        /// <summary>
        /// Validates a contract quantity against constraints
        /// </summary>
        /// <param name="contracts">Contract quantity to validate</param>
        /// <returns>True if valid, false otherwise</returns>
        public bool IsValidQuantity(int contracts)
        {
            if (contracts < MinContracts || contracts > MaxContracts)
                return false;

            if (EnforceLotSize && LotSize > 1)
            {
                if (contracts % LotSize != 0)
                    return false;
            }

            return true;
        }
    }
}
