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

namespace NT8.Core.Intelligence
{
    /// <summary>
    /// Detects trend regime and trend quality from recent bar data and AVWAP context.
    /// </summary>
    public class TrendRegimeDetector
    {
        private readonly ILogger _logger;
        private readonly object _lock = new object();
        private readonly Dictionary<string, TrendRegime> _currentRegimes;
        private readonly Dictionary<string, double> _currentStrength;

        /// <summary>
        /// Creates a new trend regime detector.
        /// </summary>
        /// <param name="logger">Logger instance.</param>
        public TrendRegimeDetector(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _currentRegimes = new Dictionary<string, TrendRegime>();
            _currentStrength = new Dictionary<string, double>();
        }

        /// <summary>
        /// Detects trend regime for a symbol based on bars and AVWAP value.
        /// </summary>
        /// <param name="symbol">Instrument symbol.</param>
        /// <param name="bars">Recent bars in chronological order.</param>
        /// <param name="avwap">Current AVWAP value.</param>
        /// <returns>Detected trend regime.</returns>
        public TrendRegime DetectTrend(string symbol, List<BarData> bars, double avwap)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");
            if (bars == null)
                throw new ArgumentNullException("bars");
            if (bars.Count < 5)
                throw new ArgumentException("At least 5 bars are required", "bars");

            try
            {
                var strength = CalculateTrendStrength(bars, avwap);
                var regime = ClassifyTrendRegime(strength);

                lock (_lock)
                {
                    _currentRegimes[symbol] = regime;
                    _currentStrength[symbol] = strength;
                }

                _logger.LogDebug("Trend regime detected for {0}: Regime={1}, Strength={2:F4}", symbol, regime, strength);
                return regime;
            }
            catch (Exception ex)
            {
                _logger.LogError("DetectTrend failed for {0}: {1}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Calculates trend strength in range [-1.0, +1.0].
        /// Positive values indicate uptrend and negative values indicate downtrend.
        /// </summary>
        /// <param name="bars">Recent bars in chronological order.</param>
        /// <param name="avwap">Current AVWAP value.</param>
        /// <returns>Trend strength score.</returns>
        public double CalculateTrendStrength(List<BarData> bars, double avwap)
        {
            if (bars == null)
                throw new ArgumentNullException("bars");
            if (bars.Count < 5)
                throw new ArgumentException("At least 5 bars are required", "bars");

            try
            {
                var last = bars[bars.Count - 1];

                var lookback = bars.Count >= 10 ? 10 : bars.Count;
                var past = bars[bars.Count - lookback];
                var slopePerBar = (last.Close - past.Close) / lookback;

                var range = EstimateAverageRange(bars, lookback);
                var normalizedSlope = range > 0.0 ? slopePerBar / range : 0.0;

                var aboveAvwapCount = 0;
                var belowAvwapCount = 0;
                var startIndex = bars.Count - lookback;
                for (var i = startIndex; i < bars.Count; i++)
                {
                    if (bars[i].Close > avwap)
                        aboveAvwapCount++;
                    else if (bars[i].Close < avwap)
                        belowAvwapCount++;
                }

                var avwapBias = 0.0;
                if (lookback > 0)
                    avwapBias = (double)(aboveAvwapCount - belowAvwapCount) / lookback;

                var structureBias = CalculateStructureBias(bars, lookback);

                var strength = (normalizedSlope * 0.45) + (avwapBias * 0.35) + (structureBias * 0.20);
                strength = Clamp(strength, -1.0, 1.0);
                return strength;
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculateTrendStrength failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Determines whether bars are ranging based on normalized trend strength threshold.
        /// </summary>
        /// <param name="bars">Recent bars.</param>
        /// <param name="threshold">Absolute strength threshold that defines range state.</param>
        /// <returns>True when market appears to be ranging.</returns>
        public bool IsRanging(List<BarData> bars, double threshold)
        {
            if (bars == null)
                throw new ArgumentNullException("bars");
            if (bars.Count < 5)
                throw new ArgumentException("At least 5 bars are required", "bars");
            if (threshold <= 0.0)
                throw new ArgumentException("threshold must be greater than zero", "threshold");

            try
            {
                var avwap = bars[bars.Count - 1].Close;
                var strength = CalculateTrendStrength(bars, avwap);
                return Math.Abs(strength) < threshold;
            }
            catch (Exception ex)
            {
                _logger.LogError("IsRanging failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Assesses trend quality using structure consistency and volatility noise.
        /// </summary>
        /// <param name="bars">Recent bars.</param>
        /// <returns>Trend quality classification.</returns>
        public TrendQuality AssessTrendQuality(List<BarData> bars)
        {
            if (bars == null)
                throw new ArgumentNullException("bars");
            if (bars.Count < 5)
                throw new ArgumentException("At least 5 bars are required", "bars");

            try
            {
                var lookback = bars.Count >= 10 ? 10 : bars.Count;
                var structure = Math.Abs(CalculateStructureBias(bars, lookback));
                var noise = CalculateNoiseRatio(bars, lookback);

                var qualityScore = (structure * 0.65) + ((1.0 - noise) * 0.35);
                qualityScore = Clamp(qualityScore, 0.0, 1.0);

                if (qualityScore >= 0.80)
                    return TrendQuality.Excellent;
                if (qualityScore >= 0.60)
                    return TrendQuality.Good;
                if (qualityScore >= 0.40)
                    return TrendQuality.Fair;

                return TrendQuality.Poor;
            }
            catch (Exception ex)
            {
                _logger.LogError("AssessTrendQuality failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets last detected trend regime for a symbol.
        /// </summary>
        /// <param name="symbol">Instrument symbol.</param>
        /// <returns>Current trend regime or Range when unknown.</returns>
        public TrendRegime GetCurrentTrendRegime(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            lock (_lock)
            {
                TrendRegime regime;
                if (_currentRegimes.TryGetValue(symbol, out regime))
                    return regime;

                return TrendRegime.Range;
            }
        }

        /// <summary>
        /// Gets last detected trend strength for a symbol.
        /// </summary>
        /// <param name="symbol">Instrument symbol.</param>
        /// <returns>Trend strength or zero when unknown.</returns>
        public double GetCurrentTrendStrength(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            lock (_lock)
            {
                double strength;
                if (_currentStrength.TryGetValue(symbol, out strength))
                    return strength;

                return 0.0;
            }
        }

        private static TrendRegime ClassifyTrendRegime(double strength)
        {
            if (strength >= 0.80)
                return TrendRegime.StrongUp;
            if (strength >= 0.30)
                return TrendRegime.WeakUp;
            if (strength <= -0.80)
                return TrendRegime.StrongDown;
            if (strength <= -0.30)
                return TrendRegime.WeakDown;

            return TrendRegime.Range;
        }

        private static double EstimateAverageRange(List<BarData> bars, int lookback)
        {
            if (lookback <= 0)
                return 0.0;

            var sum = 0.0;
            var start = bars.Count - lookback;
            for (var i = start; i < bars.Count; i++)
            {
                sum += (bars[i].High - bars[i].Low);
            }

            return sum / lookback;
        }

        private static double CalculateStructureBias(List<BarData> bars, int lookback)
        {
            var start = bars.Count - lookback;
            var upStructure = 0;
            var downStructure = 0;

            for (var i = start + 1; i < bars.Count; i++)
            {
                var prev = bars[i - 1];
                var cur = bars[i];

                var higherHigh = cur.High > prev.High;
                var higherLow = cur.Low > prev.Low;
                var lowerHigh = cur.High < prev.High;
                var lowerLow = cur.Low < prev.Low;

                if (higherHigh && higherLow)
                    upStructure++;
                else if (lowerHigh && lowerLow)
                    downStructure++;
            }

            var transitions = lookback - 1;
            if (transitions <= 0)
                return 0.0;

            return (double)(upStructure - downStructure) / transitions;
        }

        private static double CalculateNoiseRatio(List<BarData> bars, int lookback)
        {
            var start = bars.Count - lookback;
            var directionalMove = Math.Abs(bars[bars.Count - 1].Close - bars[start].Close);

            var path = 0.0;
            for (var i = start + 1; i < bars.Count; i++)
            {
                path += Math.Abs(bars[i].Close - bars[i - 1].Close);
            }

            if (path <= 0.0)
                return 1.0;

            var efficiency = directionalMove / path;
            efficiency = Clamp(efficiency, 0.0, 1.0);
            return 1.0 - efficiency;
        }

        private static double Clamp(double value, double min, double max)
        {
            if (value < min)
                return min;
            if (value > max)
                return max;
            return value;
        }
    }
}
