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

namespace NT8.Core.Intelligence
{
    /// <summary>
    /// Historical statistics for confluence scoring.
    /// </summary>
    public class ConfluenceStatistics
    {
        /// <summary>
        /// Total number of score calculations observed.
        /// </summary>
        public int TotalCalculations { get; set; }

        /// <summary>
        /// Average weighted score across history.
        /// </summary>
        public double AverageWeightedScore { get; set; }

        /// <summary>
        /// Average raw score across history.
        /// </summary>
        public double AverageRawScore { get; set; }

        /// <summary>
        /// Best weighted score seen in history.
        /// </summary>
        public double BestWeightedScore { get; set; }

        /// <summary>
        /// Worst weighted score seen in history.
        /// </summary>
        public double WorstWeightedScore { get; set; }

        /// <summary>
        /// Grade distribution counts for historical scores.
        /// </summary>
        public Dictionary<TradeGrade, int> GradeDistribution { get; set; }

        /// <summary>
        /// Last calculation timestamp in UTC.
        /// </summary>
        public DateTime LastCalculatedAtUtc { get; set; }

        /// <summary>
        /// Creates a new statistics model.
        /// </summary>
        /// <param name="totalCalculations">Total number of calculations.</param>
        /// <param name="averageWeightedScore">Average weighted score.</param>
        /// <param name="averageRawScore">Average raw score.</param>
        /// <param name="bestWeightedScore">Best weighted score.</param>
        /// <param name="worstWeightedScore">Worst weighted score.</param>
        /// <param name="gradeDistribution">Grade distribution map.</param>
        /// <param name="lastCalculatedAtUtc">Last calculation time.</param>
        public ConfluenceStatistics(
            int totalCalculations,
            double averageWeightedScore,
            double averageRawScore,
            double bestWeightedScore,
            double worstWeightedScore,
            Dictionary<TradeGrade, int> gradeDistribution,
            DateTime lastCalculatedAtUtc)
        {
            TotalCalculations = totalCalculations;
            AverageWeightedScore = averageWeightedScore;
            AverageRawScore = averageRawScore;
            BestWeightedScore = bestWeightedScore;
            WorstWeightedScore = worstWeightedScore;
            GradeDistribution = gradeDistribution ?? new Dictionary<TradeGrade, int>();
            LastCalculatedAtUtc = lastCalculatedAtUtc;
        }
    }

    /// <summary>
    /// Calculates weighted confluence score and trade grade from factor calculators.
    /// Thread-safe for weight updates and score history tracking.
    /// </summary>
    public class ConfluenceScorer
    {
        private readonly ILogger _logger;
        private readonly object _lock = new object();
        private readonly Dictionary<FactorType, double> _factorWeights;
        private readonly Queue<ConfluenceScore> _history;
        private readonly int _maxHistory;

        /// <summary>
        /// Creates a new confluence scorer instance.
        /// </summary>
        /// <param name="logger">Logger instance.</param>
        /// <param name="maxHistory">Maximum historical score entries to keep.</param>
        /// <exception cref="ArgumentNullException">Logger is null.</exception>
        /// <exception cref="ArgumentException">Max history is not positive.</exception>
        public ConfluenceScorer(ILogger logger, int maxHistory)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");
            if (maxHistory <= 0)
                throw new ArgumentException("maxHistory must be greater than zero", "maxHistory");

            _logger = logger;
            _maxHistory = maxHistory;
            _factorWeights = new Dictionary<FactorType, double>();
            _history = new Queue<ConfluenceScore>();

            _factorWeights.Add(FactorType.Setup, 1.0);
            _factorWeights.Add(FactorType.Trend, 1.0);
            _factorWeights.Add(FactorType.Volatility, 1.0);
            _factorWeights.Add(FactorType.Timing, 1.0);
            _factorWeights.Add(FactorType.ExecutionQuality, 1.0);
        }

        /// <summary>
        /// Calculates a confluence score from calculators and the current bar.
        /// </summary>
        /// <param name="intent">Strategy intent under evaluation.</param>
        /// <param name="context">Strategy context and market/session state.</param>
        /// <param name="bar">Current bar used for factor calculations.</param>
        /// <param name="factors">Factor calculator collection.</param>
        /// <returns>Calculated confluence score with grade and factor breakdown.</returns>
        /// <exception cref="ArgumentNullException">Any required parameter is null.</exception>
        public ConfluenceScore CalculateScore(
            StrategyIntent intent,
            StrategyContext context,
            BarData bar,
            List<IFactorCalculator> factors)
        {
            if (intent == null)
                throw new ArgumentNullException("intent");
            if (context == null)
                throw new ArgumentNullException("context");
            if (bar == null)
                throw new ArgumentNullException("bar");
            if (factors == null)
                throw new ArgumentNullException("factors");

            try
            {
                var calculatedFactors = new List<ConfluenceFactor>();
                var rawScoreSum = 0.0;
                var weightedScoreSum = 0.0;
                var totalWeight = 0.0;

                for (var i = 0; i < factors.Count; i++)
                {
                    var calculator = factors[i];
                    if (calculator == null)
                    {
                        continue;
                    }

                    var factor = calculator.Calculate(intent, context, bar);
                    if (factor == null)
                    {
                        continue;
                    }

                    var effectiveWeight = ResolveWeight(factor.Type, factor.Weight);

                    var weightedFactor = new ConfluenceFactor(
                        factor.Type,
                        factor.Name,
                        factor.Score,
                        effectiveWeight,
                        factor.Reason,
                        factor.Details);

                    calculatedFactors.Add(weightedFactor);

                    rawScoreSum += weightedFactor.Score;
                    weightedScoreSum += (weightedFactor.Score * weightedFactor.Weight);
                    totalWeight += weightedFactor.Weight;
                }

                var rawScore = calculatedFactors.Count > 0 ? rawScoreSum / calculatedFactors.Count : 0.0;
                var weightedScore = totalWeight > 0.0 ? weightedScoreSum / totalWeight : 0.0;

                weightedScore = ClampScore(weightedScore);
                rawScore = ClampScore(rawScore);

                var grade = MapScoreToGrade(weightedScore);

                var metadata = new Dictionary<string, object>();
                metadata.Add("factor_count", calculatedFactors.Count);
                metadata.Add("total_weight", totalWeight);
                metadata.Add("raw_score_sum", rawScoreSum);
                metadata.Add("weighted_score_sum", weightedScoreSum);

                var result = new ConfluenceScore(
                    rawScore,
                    weightedScore,
                    grade,
                    calculatedFactors,
                    DateTime.UtcNow,
                    metadata);

                lock (_lock)
                {
                    _history.Enqueue(result);
                    while (_history.Count > _maxHistory)
                    {
                        _history.Dequeue();
                    }
                }

                _logger.LogDebug(
                    "Confluence score calculated: Symbol={0}, Raw={1:F4}, Weighted={2:F4}, Grade={3}, Factors={4}",
                    intent.Symbol,
                    rawScore,
                    weightedScore,
                    grade,
                    calculatedFactors.Count);

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

        /// <summary>
        /// Calculates a confluence score using the current bar in context custom data.
        /// </summary>
        /// <param name="intent">Strategy intent under evaluation.</param>
        /// <param name="context">Strategy context that contains current bar in custom data key 'current_bar'.</param>
        /// <param name="factors">Factor calculator collection.</param>
        /// <returns>Calculated confluence score.</returns>
        /// <exception cref="ArgumentNullException">Any required parameter is null.</exception>
        /// <exception cref="ArgumentException">Current bar is missing in context custom data.</exception>
        public ConfluenceScore CalculateScore(
            StrategyIntent intent,
            StrategyContext context,
            List<IFactorCalculator> factors)
        {
            if (intent == null)
                throw new ArgumentNullException("intent");
            if (context == null)
                throw new ArgumentNullException("context");
            if (factors == null)
                throw new ArgumentNullException("factors");

            try
            {
                BarData bar = null;
                if (context.CustomData != null && context.CustomData.ContainsKey("current_bar"))
                {
                    bar = context.CustomData["current_bar"] as BarData;
                }

                if (bar == null)
                    throw new ArgumentException("context.CustomData must include key 'current_bar' with BarData value", "context");

                return CalculateScore(intent, context, bar, factors);
            }
            catch (Exception ex)
            {
                _logger.LogError("Confluence scoring failed when reading current_bar: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Maps weighted score to trade grade.
        /// </summary>
        /// <param name="weightedScore">Weighted score in range [0.0, 1.0].</param>
        /// <returns>Mapped trade grade.</returns>
        public TradeGrade MapScoreToGrade(double weightedScore)
        {
            try
            {
                var score = ClampScore(weightedScore);

                if (score >= 0.90)
                    return TradeGrade.APlus;
                if (score >= 0.80)
                    return TradeGrade.A;
                if (score >= 0.70)
                    return TradeGrade.B;
                if (score >= 0.60)
                    return TradeGrade.C;
                if (score >= 0.50)
                    return TradeGrade.D;

                return TradeGrade.F;
            }
            catch (Exception ex)
            {
                _logger.LogError("MapScoreToGrade failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Updates factor weights for one or more factor types.
        /// </summary>
        /// <param name="weights">Weight overrides by factor type.</param>
        /// <exception cref="ArgumentNullException">Weights dictionary is null.</exception>
        /// <exception cref="ArgumentException">Any weight is not positive.</exception>
        public void UpdateFactorWeights(Dictionary<FactorType, double> weights)
        {
            if (weights == null)
                throw new ArgumentNullException("weights");

            try
            {
                lock (_lock)
                {
                    foreach (var pair in weights)
                    {
                        if (pair.Value <= 0.0)
                            throw new ArgumentException("All weights must be greater than zero", "weights");

                        if (_factorWeights.ContainsKey(pair.Key))
                        {
                            _factorWeights[pair.Key] = pair.Value;
                        }
                        else
                        {
                            _factorWeights.Add(pair.Key, pair.Value);
                        }
                    }
                }

                _logger.LogInformation("Confluence factor weights updated. Count={0}", weights.Count);
            }
            catch (Exception ex)
            {
                _logger.LogError("UpdateFactorWeights failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Returns historical confluence scoring statistics.
        /// </summary>
        /// <returns>Historical confluence statistics snapshot.</returns>
        public ConfluenceStatistics GetHistoricalStats()
        {
            try
            {
                lock (_lock)
                {
                    if (_history.Count == 0)
                    {
                        return CreateEmptyStatistics();
                    }

                    var total = _history.Count;
                    var weightedSum = 0.0;
                    var rawSum = 0.0;
                    var best = 0.0;
                    var worst = 1.0;
                    var gradeDistribution = InitializeGradeDistribution();
                    DateTime last = DateTime.MinValue;

                    foreach (var score in _history)
                    {
                        weightedSum += score.WeightedScore;
                        rawSum += score.RawScore;

                        if (score.WeightedScore > best)
                            best = score.WeightedScore;
                        if (score.WeightedScore < worst)
                            worst = score.WeightedScore;

                        if (!gradeDistribution.ContainsKey(score.Grade))
                            gradeDistribution.Add(score.Grade, 0);

                        gradeDistribution[score.Grade] = gradeDistribution[score.Grade] + 1;

                        if (score.CalculatedAt > last)
                            last = score.CalculatedAt;
                    }

                    return new ConfluenceStatistics(
                        total,
                        weightedSum / total,
                        rawSum / total,
                        best,
                        worst,
                        gradeDistribution,
                        last);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("GetHistoricalStats failed: {0}", ex.Message);
                throw;
            }
        }

        private static double ClampScore(double score)
        {
            if (score < 0.0)
                return 0.0;
            if (score > 1.0)
                return 1.0;
            return score;
        }

        private double ResolveWeight(FactorType type, double fallbackWeight)
        {
            lock (_lock)
            {
                if (_factorWeights.ContainsKey(type))
                    return _factorWeights[type];
            }

            return fallbackWeight > 0.0 ? fallbackWeight : 1.0;
        }

        private ConfluenceStatistics CreateEmptyStatistics()
        {
            return new ConfluenceStatistics(
                0,
                0.0,
                0.0,
                0.0,
                0.0,
                InitializeGradeDistribution(),
                DateTime.MinValue);
        }

        private Dictionary<TradeGrade, int> InitializeGradeDistribution()
        {
            var distribution = new Dictionary<TradeGrade, int>();
            distribution.Add(TradeGrade.APlus, 0);
            distribution.Add(TradeGrade.A, 0);
            distribution.Add(TradeGrade.B, 0);
            distribution.Add(TradeGrade.C, 0);
            distribution.Add(TradeGrade.D, 0);
            distribution.Add(TradeGrade.F, 0);
            return distribution;
        }
    }
}
