using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NT8.Core.Common.Models;

namespace NT8.Core.MarketData
{
    /// <summary>
    /// Base implementation of market data provider with caching and validation
    /// </summary>
    public class MarketDataProvider : IMarketDataProvider
    {
        // Data storage
        private readonly ConcurrentDictionary<string, List<BarData>> _barCache;
        private readonly ConcurrentDictionary<string, List<TickData>> _tickCache;
        private readonly ConcurrentDictionary<string, double> _currentPrices;

        // Subscriptions
        private readonly ConcurrentDictionary<string, List<Action<BarData>>> _barSubscriptions;
        private readonly ConcurrentDictionary<string, List<Action<TickData>>> _tickSubscriptions;

        // Configuration
        private readonly int _maxCacheSize;
        private readonly TimeSpan _dataFreshnessTimeout;

        /// <summary>
        /// Constructor for MarketDataProvider
        /// </summary>
        public MarketDataProvider(int maxCacheSize = 10000, TimeSpan? dataFreshnessTimeout = null)
        {
            _barCache = new ConcurrentDictionary<string, List<BarData>>();
            _tickCache = new ConcurrentDictionary<string, List<TickData>>();
            _currentPrices = new ConcurrentDictionary<string, double>();
            _barSubscriptions = new ConcurrentDictionary<string, List<Action<BarData>>>();
            _tickSubscriptions = new ConcurrentDictionary<string, List<Action<TickData>>>();

            _maxCacheSize = maxCacheSize;
            _dataFreshnessTimeout = dataFreshnessTimeout ?? TimeSpan.FromMinutes(5);
        }

        /// <summary>
        /// Subscribe to bar data
        /// </summary>
        public void SubscribeBars(string symbol, TimeSpan barSize, Action<BarData> onBar)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", "symbol");

            if (onBar == null)
                throw new ArgumentNullException("onBar");

            string key = string.Format("{0}_{1}", symbol, barSize);
            _barSubscriptions.AddOrUpdate(
                key,
                new List<Action<BarData>> { onBar },
                (k, list) => { list.Add(onBar); return list; }
            );
        }

        /// <summary>
        /// Subscribe to tick data
        /// </summary>
        public void SubscribeTicks(string symbol, Action<TickData> onTick)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", "symbol");

            if (onTick == null)
                throw new ArgumentNullException("onTick");

            _tickSubscriptions.AddOrUpdate(
                symbol,
                new List<Action<TickData>> { onTick },
                (k, list) => { list.Add(onTick); return list; }
            );
        }

        /// <summary>
        /// Get historical bars
        /// </summary>
        public async Task<List<BarData>> GetHistoricalBars(string symbol, TimeSpan barSize, int count)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", "symbol");

            if (count <= 0)
                throw new ArgumentException("Count must be greater than zero", "count");

            string key = string.Format("{0}_{1}", symbol, barSize);

            List<BarData> bars;
            if (_barCache.TryGetValue(key, out bars))
            {
                // Return the most recent bars, up to the requested count
                var result = bars
                    .OrderByDescending(b => b.Time)
                    .Take(count)
                    .ToList();

                return await Task.FromResult(result);
            }

            // Return empty list if no data is available
            return await Task.FromResult(new List<BarData>());
        }

        /// <summary>
        /// Get current market price
        /// </summary>
        public double? GetCurrentPrice(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentException("Symbol cannot be null or empty", "symbol");

            double price;
            if (_currentPrices.TryGetValue(symbol, out price))
            {
                return price;
            }
            return null;
        }

        /// <summary>
        /// Add bar data to the provider
        /// </summary>
        public void AddBarData(BarData bar)
        {
            if (bar == null)
                throw new ArgumentNullException("bar");

            string key = string.Format("{0}_{1}", bar.Symbol, bar.BarSize);

            // Add to cache
            _barCache.AddOrUpdate(
                key,
                new List<BarData> { bar },
                (k, list) =>
                {
                    list.Add(bar);
                    // Trim cache if it exceeds maximum size
                    if (list.Count > _maxCacheSize)
                    {
                        list.RemoveRange(0, list.Count - _maxCacheSize);
                    }
                    return list;
                }
            );

            // Update current price
            _currentPrices[bar.Symbol] = bar.Close;

            // Notify subscribers
            List<Action<BarData>> subscribers;
            if (_barSubscriptions.TryGetValue(key, out subscribers))
            {
                foreach (var subscriber in subscribers)
                {
                    try
                    {
                        subscriber(bar);
                    }
                    catch
                    {
                        // Ignore exceptions in subscriber callbacks
                    }
                }
            }
        }

        /// <summary>
        /// Add tick data to the provider
        /// </summary>
        public void AddTickData(TickData tick)
        {
            if (tick == null)
                throw new ArgumentNullException("tick");

            // Add to cache
            _tickCache.AddOrUpdate(
                tick.Symbol,
                new List<TickData> { tick },
                (k, list) =>
                {
                    list.Add(tick);
                    // Trim cache if it exceeds maximum size
                    if (list.Count > _maxCacheSize)
                    {
                        list.RemoveRange(0, list.Count - _maxCacheSize);
                    }
                    return list;
                }
            );

            // Update current price
            _currentPrices[tick.Symbol] = tick.Price;

            // Notify subscribers
            List<Action<TickData>> subscribers;
            if (_tickSubscriptions.TryGetValue(tick.Symbol, out subscribers))
            {
                foreach (var subscriber in subscribers)
                {
                    try
                    {
                        subscriber(tick);
                    }
                    catch
                    {
                        // Ignore exceptions in subscriber callbacks
                    }
                }
            }
        }

        /// <summary>
        /// Validate market data quality
        /// </summary>
        public bool ValidateDataQuality(BarData bar)
        {
            if (bar == null)
                return false;

            // Check for reasonable price values
            if (bar.Open <= 0 || bar.High <= 0 || bar.Low <= 0 || bar.Close <= 0)
                return false;

            // Check for valid high/low relationships
            if (bar.High < bar.Low)
                return false;

            // Check if close price is within high/low range
            if (bar.Close < bar.Low || bar.Close > bar.High)
                return false;

            // Check for reasonable volume
            if (bar.Volume < 0)
                return false;

            return true;
        }

        /// <summary>
        /// Validate market data quality
        /// </summary>
        public bool ValidateDataQuality(TickData tick)
        {
            if (tick == null)
                return false;

            // Check for reasonable price values
            if (tick.Price <= 0)
                return false;

            // Check for reasonable size
            if (tick.Size < 0)
                return false;

            return true;
        }

        /// <summary>
        /// Get data freshness information
        /// </summary>
        public DateTime? GetLastUpdateTime(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                return null;

            // Check bars for this symbol
            var barKeys = _barCache.Keys.Where(k => k.StartsWith(string.Format("{0}_", symbol))).ToList();
            DateTime? latestBarTime = null;

            foreach (var key in barKeys)
            {
                List<BarData> bars;
                if (_barCache.TryGetValue(key, out bars) && bars.Count > 0)
                {
                    var lastBarTime = bars.Max(b => b.Time);
                    if (latestBarTime == null || lastBarTime > latestBarTime)
                    {
                        latestBarTime = lastBarTime;
                    }
                }
            }

            // Check ticks for this symbol
            List<TickData> ticks;
            if (_tickCache.TryGetValue(symbol, out ticks) && ticks.Count > 0)
            {
                var lastTickTime = ticks.Max(t => t.Time);
                if (latestBarTime == null || lastTickTime > latestBarTime)
                {
                    latestBarTime = lastTickTime;
                }
            }

            return latestBarTime;
        }

        /// <summary>
        /// Check if data is fresh
        /// </summary>
        public bool IsDataFresh(string symbol)
        {
            DateTime? lastUpdate = GetLastUpdateTime(symbol);
            if (lastUpdate == null)
                return false;

            return DateTime.UtcNow - lastUpdate.Value < _dataFreshnessTimeout;
        }
    }
}
