using TradingBot.Models;

namespace TradingBot.Services;

public class TradingBotService
{
    private readonly IMarketDataService _marketDataService;
    private readonly ITradingStrategy _strategy;
    private readonly TradeHistoryService _historyService;
    private readonly LoggingService _loggingService;
    private readonly Dictionary<string, AssetConfiguration> _assetConfigs = new();
    private readonly Dictionary<string, AssetStatistics> _assetStats = new();
    private readonly List<Trade> _trades = new();
    private readonly Dictionary<string, List<MarketPrice>> _priceHistory = new();
    private readonly Dictionary<string, TechnicalIndicators> _indicators = new();
    private readonly Dictionary<string, Trade> _activePositions = new();
    private Timer? _timer;
    private Timer? _persistenceTimer;
    
    public BotStatus Status { get; private set; } = new();
    public IReadOnlyList<Trade> Trades => _trades.AsReadOnly();
    public IReadOnlyDictionary<string, AssetConfiguration> AssetConfigurations => _assetConfigs;
    public IReadOnlyDictionary<string, AssetStatistics> AssetStatistics => _assetStats;
    public IReadOnlyDictionary<string, Trade> ActivePositions => _activePositions;
    
    public event Action? OnStatusChanged;
    public event Action<TradingSignal>? OnSignalGenerated;
    public event Action<Trade>? OnTradeExecuted;
    public event Action<string, TechnicalIndicators>? OnIndicatorsUpdated;
    public event Action<string, MarketPrice>? OnPriceUpdated;
    public event Action? OnStatisticsUpdated;

    public TradingBotService(
        IMarketDataService marketDataService, 
        ITradingStrategy strategy,
        TradeHistoryService historyService,
        LoggingService loggingService)
    {
        _marketDataService = marketDataService;
        _strategy = strategy;
        _historyService = historyService;
        _loggingService = loggingService;
        Status.CurrentStrategy = strategy.Name;
        
        // Subscribe to simulated market updates if available
        if (_marketDataService is SimulatedMarketDataService simService)
        {
            simService.OnPriceUpdated += HandleSimulatedPriceUpdate;
        }
        
        InitializeDefaultAssets();
        
        // Load persisted data
        _ = LoadPersistedDataAsync();
        
        _loggingService.LogInfo("System", "TradingBot Service initialized");
    }

    private async Task LoadPersistedDataAsync()
    {
        try
        {
            // Load trade history
            var trades = await _historyService.LoadTradeHistoryAsync();
            _trades.AddRange(trades);
            
            // Load active positions
            var positions = await _historyService.LoadActivePositionsAsync();
            foreach (var kvp in positions)
            {
                _activePositions[kvp.Key] = kvp.Value;
            }
            
            // Restore asset configurations from active positions
            RestoreAssetConfigurationsFromTrades();
            
            OnStatusChanged?.Invoke();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading persisted data: {ex.Message}");
        }
    }

    private void RestoreAssetConfigurationsFromTrades()
    {
        foreach (var position in _activePositions.Values)
        {
            if (_assetConfigs.TryGetValue(position.Symbol, out var config))
            {
                if (position.Type == TradeType.Buy)
                {
                    config.CurrentHoldings += position.Amount;
                    config.AverageEntryPrice = position.Price;
                }
            }
        }
    }

    private void InitializeDefaultAssets()
    {
        // Get available symbols from SimulatedMarketDataService
        var availableSymbols = _marketDataService is SimulatedMarketDataService simService
            ? simService.GetAvailableSymbols()
            : new List<string> { "BTC", "ETH", "SOL", "ADA", "MATIC" };
        
        var assetNames = _marketDataService is SimulatedMarketDataService simService2
            ? simService2.GetAssetNames()
            : new Dictionary<string, string>
            {
                { "BTC", "Bitcoin" },
                { "ETH", "Ethereum" },
                { "SOL", "Solana" },
                { "ADA", "Cardano" },
                { "MATIC", "Polygon" }
            };

        foreach (var symbol in availableSymbols)
        {
            _assetConfigs[symbol] = new AssetConfiguration
            {
                Symbol = symbol,
                Name = assetNames.TryGetValue(symbol, out var name) ? name : symbol,
                IsEnabled = true,
                InitialBalance = 1000m,
                CurrentBalance = 1000m
            };

            _assetStats[symbol] = new AssetStatistics
            {
                Symbol = symbol,
                Name = assetNames.TryGetValue(symbol, out var name2) ? name2 : symbol
            };
        }
    }

    public void UpdateAssetConfiguration(string symbol, AssetConfiguration config)
    {
        _assetConfigs[symbol] = config;
        OnStatusChanged?.Invoke();
    }

    public void ToggleAsset(string symbol, bool enabled)
    {
        if (_assetConfigs.TryGetValue(symbol, out var config))
        {
            config.IsEnabled = enabled;
            OnStatusChanged?.Invoke();
        }
    }

    public void AddAsset(string symbol, string name)
    {
        if (!_assetConfigs.ContainsKey(symbol))
        {
            _assetConfigs[symbol] = new AssetConfiguration
            {
                Symbol = symbol,
                Name = name,
                IsEnabled = false,
                InitialBalance = 1000m,
                CurrentBalance = 1000m
            };

            _assetStats[symbol] = new AssetStatistics
            {
                Symbol = symbol,
                Name = name
            };
            
            OnStatusChanged?.Invoke();
        }
    }

    public void Start()
    {
        if (Status.IsRunning) return;

        Status.IsRunning = true;
        Status.StartedAt = DateTime.UtcNow;
        
        _loggingService.LogInfo("Bot", "Trading Bot started", $"Strategy: {_strategy.Name}");
        
        // Reset daily trade counts
        foreach (var config in _assetConfigs.Values)
        {
            if (config.DailyTradeCountReset.Date < DateTime.UtcNow.Date)
            {
                config.DailyTradeCount = 0;
                config.DailyTradeCountReset = DateTime.UtcNow.Date;
            }
        }
        
        // Start update timer (every 3 seconds for simulation)
        _timer = new Timer(async _ => await UpdateAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(3));
        
        // Start persistence timer (save every 30 seconds)
        _persistenceTimer = new Timer(
            async _ => await SaveDataAsync(), 
            null, 
            TimeSpan.FromSeconds(30), 
            TimeSpan.FromSeconds(30));
        
        OnStatusChanged?.Invoke();
    }

    public async void Stop()
    {
        if (!Status.IsRunning) return;

        Status.IsRunning = false;
        _timer?.Dispose();
        _timer = null;
        
        _persistenceTimer?.Dispose();
        _persistenceTimer = null;
        
        _loggingService.LogInfo("Bot", "Trading Bot stopped", $"Total trades: {_trades.Count}");
        
        // Save data on stop
        await SaveDataAsync();
        
        OnStatusChanged?.Invoke();
    }

    private async Task SaveDataAsync()
    {
        try
        {
            await _historyService.SaveTradeHistoryAsync(_trades);
            await _historyService.SaveActivePositionsAsync(_activePositions);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error saving data: {ex.Message}");
        }
    }

    private void HandleSimulatedPriceUpdate()
    {
        if (Status.IsRunning)
        {
            _ = UpdateAsync();
        }
    }

    private async Task UpdateAsync()
    {
        try
        {
            var enabledSymbols = _assetConfigs.Values
                .Where(c => c != null && c.IsEnabled)
                .Select(c => c.Symbol)
                .Where(s => !string.IsNullOrWhiteSpace(s))
                .ToList();

            if (enabledSymbols.Count == 0) return;

            var prices = await _marketDataService.GetMarketPricesAsync(enabledSymbols);
            
            if (prices == null) return;

            foreach (var price in prices)
            {
                if (price != null)
                {
                    await ProcessAssetUpdate(price);
                }
            }

            UpdateGlobalStatistics();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error in UpdateAsync: {ex.Message}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
        }
    }

    private async Task ProcessAssetUpdate(MarketPrice price)
    {
        if (price == null || price.Price <= 0)
            return;

        if (!_assetConfigs.TryGetValue(price.Symbol, out var config) || !config.IsEnabled)
            return;

        // Update price history
        if (!_priceHistory.ContainsKey(price.Symbol))
        {
            _priceHistory[price.Symbol] = new List<MarketPrice>();
        }
        
        _priceHistory[price.Symbol].Add(price);
        
        if (_priceHistory[price.Symbol].Count > 200)
        {
            _priceHistory[price.Symbol].RemoveAt(0);
        }

        // Update statistics current price
        if (_assetStats.TryGetValue(price.Symbol, out var stats))
        {
            stats.CurrentPrice = price.Price;
        }

        OnPriceUpdated?.Invoke(price.Symbol, price);

        // Calculate indicators if enough data
        if (_priceHistory[price.Symbol].Count >= 26)
        {
            UpdateIndicators(price.Symbol);

            // Generate trading signal
            var signal = await _strategy.AnalyzeAsync(price.Symbol, _priceHistory[price.Symbol]);
            
            if (signal != null)
            {
                OnSignalGenerated?.Invoke(signal);

                // Execute trades based on strategy and configuration
                await EvaluateAndExecuteTrade(price.Symbol, signal, price, config);
            }
        }
    }

    private async Task EvaluateAndExecuteTrade(string symbol, TradingSignal signal, MarketPrice price, AssetConfiguration config)
    {
        if (!_indicators.TryGetValue(symbol, out var indicators))
            return;

        // Check daily trade limit
        if (config.DailyTradeCount >= config.MaxDailyTrades)
            return;

        // Check if enough time has passed since last trade (min 10 seconds)
        if (config.LastTradeTime.HasValue && 
            (DateTime.UtcNow - config.LastTradeTime.Value).TotalSeconds < 10)
            return;

        // Buy logic
        if (signal.Type == SignalType.Buy && 
            indicators.RSI < 40 && 
            indicators.Histogram > 0 &&
            config.CurrentBalance >= config.MinTradeAmount)
        {
            var tradeAmount = Math.Min(
                Math.Min(config.CurrentBalance * 0.3m, config.MaxTradeAmount),
                config.MaxPositionSize - (config.CurrentHoldings * price.Price)
            );

            if (tradeAmount >= config.MinTradeAmount)
            {
                await ExecuteBuyAsync(symbol, price.Price, tradeAmount, config);
            }
        }
        // Sell logic
        else if (signal.Type == SignalType.Sell && 
                 indicators.RSI > 60 && 
                 indicators.Histogram < 0 &&
                 config.CurrentHoldings > 0)
        {
            var profitPercentage = config.AverageEntryPrice > 0 
                ? ((price.Price - config.AverageEntryPrice) / config.AverageEntryPrice) * 100 
                : 0;

            // Sell if profit target reached or stop loss triggered
            if (profitPercentage >= config.TakeProfitPercentage || 
                profitPercentage <= -config.StopLossPercentage)
            {
                await ExecuteSellAsync(symbol, price.Price, config.CurrentHoldings, config);
            }
        }
    }

    private async Task ExecuteBuyAsync(string symbol, decimal price, decimal amountUSD, AssetConfiguration config)
    {
        var amount = amountUSD / price;
        
        // Update config
        var previousHoldings = config.CurrentHoldings;
        config.CurrentHoldings += amount;
        config.CurrentBalance -= amountUSD;
        config.AverageEntryPrice = previousHoldings > 0
            ? ((config.AverageEntryPrice * previousHoldings) + (price * amount)) / config.CurrentHoldings
            : price;
        config.LastTradeTime = DateTime.UtcNow;
        config.DailyTradeCount++;
        
        var trade = new Trade
        {
            Symbol = symbol,
            Type = TradeType.Buy,
            Price = price,
            Amount = amount,
            Timestamp = DateTime.UtcNow,
            Strategy = _strategy.Name,
            IsBot = true
        };

        _trades.Add(trade);
        _activePositions[symbol] = trade;
        UpdateAssetStatistics(symbol, trade);
        
        Status.TradesExecuted++;
        
        _loggingService.LogTrade(
            symbol,
            $"BUY {amount:F6} {symbol} @ ${price:N2}",
            $"Value: ${amountUSD:N2} | Balance: ${config.CurrentBalance:N2}");
        
        OnTradeExecuted?.Invoke(trade);
        OnStatusChanged?.Invoke();
        
        // Save immediately after trade
        await SaveDataAsync();
    }

    private async Task ExecuteSellAsync(string symbol, decimal price, decimal amount, AssetConfiguration config)
    {
        var amountUSD = amount * price;
        var profit = (price - config.AverageEntryPrice) * amount;
        
        // Update config
        config.CurrentHoldings = 0;
        config.CurrentBalance += amountUSD;
        config.LastTradeTime = DateTime.UtcNow;
        config.DailyTradeCount++;
        
        var trade = new Trade
        {
            Symbol = symbol,
            Type = TradeType.Sell,
            Price = price,
            Amount = amount,
            Timestamp = DateTime.UtcNow,
            Strategy = _strategy.Name,
            IsBot = true
        };

        _trades.Add(trade);
        _activePositions.Remove(symbol);
        UpdateAssetStatistics(symbol, trade, profit);
        
        Status.TradesExecuted++;
        
        _loggingService.LogTrade(
            symbol,
            $"SELL {amount:F6} {symbol} @ ${price:N2}",
            $"Value: ${amountUSD:N2} | Profit: ${profit:N2} | Balance: ${config.CurrentBalance:N2}");
        
        OnTradeExecuted?.Invoke(trade);
        OnStatusChanged?.Invoke();
        
        // Save immediately after trade
        await SaveDataAsync();
    }

    private void UpdateIndicators(string symbol)
    {
        if (!_priceHistory.TryGetValue(symbol, out var history) || history == null || history.Count < 26)
            return;

        var prices = history
            .Where(p => p != null && p.Price > 0)
            .Select(p => p.Price)
            .ToList();
        
        if (prices.Count < 26)
            return;
        
        var rsi = TechnicalAnalysis.CalculateRSI(prices);
        var (macd, signal, histogram) = TechnicalAnalysis.CalculateMACD(prices);

        var indicators = new TechnicalIndicators
        {
            RSI = rsi,
            MACD = macd,
            Signal = signal,
            Histogram = histogram,
            EMA12 = TechnicalAnalysis.CalculateEMA(prices, 12),
            EMA26 = TechnicalAnalysis.CalculateEMA(prices, 26)
        };

        _indicators[symbol] = indicators;
        OnIndicatorsUpdated?.Invoke(symbol, indicators);
    }

    private void UpdateAssetStatistics(string symbol, Trade trade, decimal? realizedProfit = null)
    {
        if (!_assetStats.TryGetValue(symbol, out var stats))
            return;

        stats.TotalTrades++;
        stats.RecentTrades.Insert(0, trade);
        
        if (stats.RecentTrades.Count > 50)
            stats.RecentTrades.RemoveAt(stats.RecentTrades.Count - 1);

        if (!stats.FirstTradeTime.HasValue)
            stats.FirstTradeTime = trade.Timestamp;
        
        stats.LastTradeTime = trade.Timestamp;

        if (realizedProfit.HasValue)
        {
            if (realizedProfit.Value > 0)
            {
                stats.WinningTrades++;
                stats.TotalProfit += realizedProfit.Value;
                stats.ConsecutiveWins++;
                stats.ConsecutiveLosses = 0;
                stats.MaxConsecutiveWins = Math.Max(stats.MaxConsecutiveWins, stats.ConsecutiveWins);
                
                if (realizedProfit.Value > stats.LargestWin)
                    stats.LargestWin = realizedProfit.Value;
            }
            else if (realizedProfit.Value < 0)
            {
                stats.LosingTrades++;
                stats.TotalLoss += Math.Abs(realizedProfit.Value);
                stats.ConsecutiveLosses++;
                stats.ConsecutiveWins = 0;
                stats.MaxConsecutiveLosses = Math.Max(stats.MaxConsecutiveLosses, stats.ConsecutiveLosses);
                
                if (Math.Abs(realizedProfit.Value) > stats.LargestLoss)
                    stats.LargestLoss = Math.Abs(realizedProfit.Value);
            }
        }

        if (_assetConfigs.TryGetValue(symbol, out var config))
        {
            stats.TotalProfit = config.TotalProfit;
            stats.ProfitPercentage = config.ProfitPercentage;
            stats.CurrentPosition = config.CurrentHoldings;
            stats.AverageEntryPrice = config.AverageEntryPrice;
        }

        OnStatisticsUpdated?.Invoke();
    }

    private void UpdateGlobalStatistics()
    {
        decimal totalProfit = 0;
        int totalTrades = 0;

        foreach (var config in _assetConfigs.Values.Where(c => c.IsEnabled))
        {
            totalProfit += config.TotalProfit;
        }

        totalTrades = _trades.Count;

        Status.TotalProfit = totalProfit;
        Status.TradesExecuted = totalTrades;
    }

    public PortfolioStatistics GetPortfolioStatistics()
    {
        var portfolio = new PortfolioStatistics
        {
            TotalAssets = _assetConfigs.Count,
            ActiveAssets = _assetConfigs.Values.Count(c => c.IsEnabled),
            TotalTrades = _trades.Count,
            AssetStatistics = _assetStats.Values.ToList(),
            StartDate = Status.StartedAt
        };

        portfolio.TotalBalance = _assetConfigs.Values.Sum(c => 
            c.CurrentBalance + (c.CurrentHoldings * (_assetStats.TryGetValue(c.Symbol, out var s) ? s.CurrentPrice : 0)));
        
        portfolio.InitialBalance = _assetConfigs.Values.Sum(c => c.InitialBalance);

        if (_assetStats.Values.Any())
        {
            var winningTrades = _assetStats.Values.Sum(s => s.WinningTrades);
            var totalTrades = _assetStats.Values.Sum(s => s.TotalTrades);
            portfolio.WinRate = totalTrades > 0 ? (decimal)winningTrades / totalTrades * 100 : 0;

            var bestAsset = _assetStats.Values.OrderByDescending(s => s.NetProfit).FirstOrDefault();
            if (bestAsset != null)
            {
                portfolio.BestPerformingAssetSymbol = bestAsset.Symbol;
                portfolio.BestPerformingAssetProfit = bestAsset.NetProfit;
            }

            var worstAsset = _assetStats.Values.OrderBy(s => s.NetProfit).FirstOrDefault();
            if (worstAsset != null)
            {
                portfolio.WorstPerformingAssetSymbol = worstAsset.Symbol;
                portfolio.WorstPerformingAssetProfit = worstAsset.NetProfit;
            }
        }

        return portfolio;
    }

    public List<MarketPrice>? GetPriceHistory(string symbol)
    {
        return _priceHistory.TryGetValue(symbol, out var history) ? history : null;
    }

    public TechnicalIndicators? GetIndicators(string symbol)
    {
        return _indicators.TryGetValue(symbol, out var indicators) ? indicators : null;
    }

    public MarketPrice? GetLatestPrice(string symbol)
    {
        if (string.IsNullOrWhiteSpace(symbol))
            return null;

        var history = GetPriceHistory(symbol);
        return history?.LastOrDefault();
    }
    
    public async Task ClearAllDataAsync()
    {
        _trades.Clear();
        _activePositions.Clear();
        _historyService.ClearAll();
        
        foreach (var config in _assetConfigs.Values)
        {
            config.CurrentBalance = config.InitialBalance;
            config.CurrentHoldings = 0;
            config.AverageEntryPrice = 0;
            config.DailyTradeCount = 0;
        }
        
        OnStatusChanged?.Invoke();
        await Task.CompletedTask;
    }
}
