using TradingBot.Models;

namespace TradingBot.Services;

public class SimulatedMarketDataService : IMarketDataService
{
    private readonly Dictionary<string, SimulatedAsset> _assets = new();
    private readonly Random _random = new();
    private readonly Timer _updateTimer;
    private readonly object _lock = new();

    public event Action? OnPriceUpdated;

    public SimulatedMarketDataService()
    {
        InitializeAssets();
        _updateTimer = new Timer(UpdatePrices, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));
    }

    private void InitializeAssets()
    {
        var assets = new[]
        {
            new { Symbol = "BTC", Name = "Bitcoin", BasePrice = 45000m, Volatility = 0.02m, TrendBias = 0.0002m },
            new { Symbol = "ETH", Name = "Ethereum", BasePrice = 2500m, Volatility = 0.025m, TrendBias = 0.0003m },
            new { Symbol = "BNB", Name = "Binance Coin", BasePrice = 350m, Volatility = 0.03m, TrendBias = 0.0001m },
            new { Symbol = "SOL", Name = "Solana", BasePrice = 100m, Volatility = 0.035m, TrendBias = 0.0004m },
            new { Symbol = "ADA", Name = "Cardano", BasePrice = 0.45m, Volatility = 0.028m, TrendBias = 0.0002m },
            new { Symbol = "XRP", Name = "Ripple", BasePrice = 0.65m, Volatility = 0.032m, TrendBias = 0.0001m },
            new { Symbol = "DOT", Name = "Polkadot", BasePrice = 6.5m, Volatility = 0.03m, TrendBias = 0.0003m },
            new { Symbol = "AVAX", Name = "Avalanche", BasePrice = 35m, Volatility = 0.038m, TrendBias = 0.0005m },
            new { Symbol = "MATIC", Name = "Polygon", BasePrice = 0.85m, Volatility = 0.033m, TrendBias = 0.0002m },
            new { Symbol = "LINK", Name = "Chainlink", BasePrice = 15m, Volatility = 0.029m, TrendBias = 0.0003m },
            new { Symbol = "UNI", Name = "Uniswap", BasePrice = 6.5m, Volatility = 0.031m, TrendBias = 0.0001m },
            new { Symbol = "ATOM", Name = "Cosmos", BasePrice = 10m, Volatility = 0.03m, TrendBias = 0.0004m },
            new { Symbol = "LTC", Name = "Litecoin", BasePrice = 75m, Volatility = 0.025m, TrendBias = 0.0001m },
            new { Symbol = "ALGO", Name = "Algorand", BasePrice = 0.25m, Volatility = 0.032m, TrendBias = 0.0003m },
            new { Symbol = "VET", Name = "VeChain", BasePrice = 0.03m, Volatility = 0.035m, TrendBias = 0.0002m }
        };

        foreach (var asset in assets)
        {
            _assets[asset.Symbol] = new SimulatedAsset
            {
                Symbol = asset.Symbol,
                Name = asset.Name,
                CurrentPrice = asset.BasePrice,
                BasePrice = asset.BasePrice,
                Volatility = asset.Volatility,
                TrendBias = asset.TrendBias,
                LastUpdate = DateTime.UtcNow
            };
        }
    }

    private void UpdatePrices(object? state)
    {
        lock (_lock)
        {
            var now = DateTime.UtcNow;
            
            foreach (var asset in _assets.Values)
            {
                // Calculate time-based factors
                var timeSinceStart = (now - asset.LastUpdate).TotalSeconds;
                
                // Generate random walk with trend
                var randomChange = (_random.NextDouble() - 0.5) * 2 * (double)asset.Volatility;
                var trendComponent = (double)asset.TrendBias;
                
                // Add market cycles (sine wave for realistic market behavior)
                var cycleComponent = Math.Sin((double)asset.PriceUpdateCount / 100.0) * 0.001;
                
                // Combine all factors
                var totalChange = randomChange + trendComponent + cycleComponent;
                
                // Update price
                var newPrice = asset.CurrentPrice * (1 + (decimal)totalChange);
                
                // Keep price within reasonable bounds (50% to 200% of base price)
                newPrice = Math.Max(asset.BasePrice * 0.5m, Math.Min(asset.BasePrice * 2.0m, newPrice));
                
                // Calculate change and volume
                var priceChange = newPrice - asset.CurrentPrice;
                var changePercentage = asset.CurrentPrice > 0 ? (priceChange / asset.CurrentPrice) * 100 : 0;
                
                // Simulate volume based on volatility and price change
                var baseVolume = asset.BasePrice * 1000000m;
                var volumeVariation = (decimal)(_random.NextDouble() * 0.5 + 0.75); // 75% to 125%
                var volumeFromVolatility = Math.Abs(changePercentage) * 100000m;
                
                asset.CurrentPrice = newPrice;
                asset.Change24h = changePercentage;
                asset.Volume24h = (baseVolume + volumeFromVolatility) * volumeVariation;
                asset.LastUpdate = now;
                asset.PriceUpdateCount++;
                
                // Add to history
                asset.PriceHistory.Add(new MarketPrice
                {
                    Symbol = asset.Symbol,
                    Price = newPrice,
                    Change24h = changePercentage,
                    Volume24h = asset.Volume24h,
                    Timestamp = now
                });
                
                // Keep history limited to last 500 points
                if (asset.PriceHistory.Count > 500)
                {
                    asset.PriceHistory.RemoveAt(0);
                }
            }
            
            OnPriceUpdated?.Invoke();
        }
    }

    public Task<List<MarketPrice>> GetMarketPricesAsync(List<string> symbols)
    {
        lock (_lock)
        {
            var prices = new List<MarketPrice>();
            
            foreach (var symbol in symbols)
            {
                if (_assets.TryGetValue(symbol, out var asset))
                {
                    prices.Add(new MarketPrice
                    {
                        Symbol = asset.Symbol,
                        Price = asset.CurrentPrice,
                        Change24h = asset.Change24h,
                        Volume24h = asset.Volume24h,
                        Timestamp = asset.LastUpdate
                    });
                }
            }
            
            return Task.FromResult(prices);
        }
    }

    public Task<MarketPrice?> GetPriceAsync(string symbol)
    {
        lock (_lock)
        {
            if (_assets.TryGetValue(symbol, out var asset))
            {
                return Task.FromResult<MarketPrice?>(new MarketPrice
                {
                    Symbol = asset.Symbol,
                    Price = asset.CurrentPrice,
                    Change24h = asset.Change24h,
                    Volume24h = asset.Volume24h,
                    Timestamp = asset.LastUpdate
                });
            }
            
            return Task.FromResult<MarketPrice?>(null);
        }
    }

    public List<MarketPrice> GetPriceHistory(string symbol, int count = 100)
    {
        lock (_lock)
        {
            if (_assets.TryGetValue(symbol, out var asset))
            {
                return asset.PriceHistory
                    .Skip(Math.Max(0, asset.PriceHistory.Count - count))
                    .ToList();
            }
            
            return new List<MarketPrice>();
        }
    }

    public List<string> GetAvailableSymbols()
    {
        lock (_lock)
        {
            return _assets.Keys.OrderBy(s => s).ToList();
        }
    }

    public Dictionary<string, string> GetAssetNames()
    {
        lock (_lock)
        {
            return _assets.ToDictionary(a => a.Key, a => a.Value.Name);
        }
    }

    private class SimulatedAsset
    {
        public string Symbol { get; set; } = string.Empty;
        public string Name { get; set; } = string.Empty;
        public decimal CurrentPrice { get; set; }
        public decimal BasePrice { get; set; }
        public decimal Change24h { get; set; }
        public decimal Volume24h { get; set; }
        public decimal Volatility { get; set; }
        public decimal TrendBias { get; set; }
        public DateTime LastUpdate { get; set; }
        public int PriceUpdateCount { get; set; }
        public List<MarketPrice> PriceHistory { get; set; } = new();
    }
}
