using Predictalytics.Application.Interfaces;
using Predictalytics.Domain.Enums;
using Predictalytics.Domain.Interfaces;
using Predictalytics.Infrastructure.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;

namespace Predictalytics.Worker.Services;

/// <summary>
/// Background service that loads and updates trade history for all tracked traders.
/// Respects a 6-hour cooldown per trader to avoid excessive API calls.
/// Links trades to MarketOutcomes via AssetId → TokenId mapping.
/// </summary>
public class TradeHistoryWorker : BackgroundService
{
    private static DateTime _lastDbError = DateTime.MinValue;
    private readonly IServiceProvider _services;
    private readonly IPlatformStatisticsService _statsService;
    private readonly ILogger<TradeHistoryWorker> _logger;

    private const int CooldownHours = 12;
    private const int TradesPerFetch = 1000;
    private const int TradersPerCycle = 100;

    public TradeHistoryWorker(IServiceProvider services, ILogger<TradeHistoryWorker> logger, IPlatformStatisticsService statsService)
    { _services = services; _logger = logger; _statsService = statsService; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("📜 TradeHistoryWorker started (update cooldown: {Hours}h)", CooldownHours);
        
        using (var scope = _services.CreateScope())
        {
            var jobRepo = scope.ServiceProvider.GetRequiredService<IJobRepository>();
            await jobRepo.ResetHungJobsAsync(stoppingToken);
        }

        await Task.Delay(15000, stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            IReadOnlyList<Domain.Entities.Trader> tradersToProcess = new List<Domain.Entities.Trader>();
            try
            {
                Domain.Entities.BackgroundJob? activeJob = null;
                
                using (var scope = _services.CreateScope())
                {
                    var jobRepo = scope.ServiceProvider.GetRequiredService<IJobRepository>();
                    activeJob = await jobRepo.GetNextPendingJobAsync(Predictalytics.Domain.Enums.JobType.DeepResync, stoppingToken);
                    if (activeJob == null)
                    {
                        activeJob = await jobRepo.GetNextPendingJobAsync(Predictalytics.Domain.Enums.JobType.HistorySync, stoppingToken);
                    }

                    var repo = scope.ServiceProvider.GetRequiredService<ITraderRepository>();

                    if (activeJob != null && activeJob.TraderId.HasValue)
                    {
                        var t = await repo.GetByIdAsync(activeJob.TraderId.Value, stoppingToken);
                        if (t != null)
                        {
                            tradersToProcess = new[] { t };
                            activeJob.Status = Predictalytics.Domain.Enums.JobStatus.InProgress;
                            activeJob.StartedAt = DateTime.UtcNow;
                            await jobRepo.UpdateAsync(activeJob, stoppingToken);
                        }
                    }
                    else
                    {
                        tradersToProcess = await repo.GetTradersDueForTradeUpdateAsync(CooldownHours, TradersPerCycle, stoppingToken);
                    }
                }

                if (tradersToProcess.Count == 0)
                {
                    _logger.LogDebug("🔄 No traders due for sync. Sleeping.");
                }
                else
                {
                    _logger.LogInformation("🔄 Processing {Count} traders for trade sync (Initial/Update)", tradersToProcess.Count);
                }

                var outcomeCache = new System.Collections.Concurrent.ConcurrentDictionary<string, Domain.Entities.MarketOutcome>();
                var marketFetchCache = new System.Collections.Concurrent.ConcurrentDictionary<string, Task<Domain.Entities.Market?>>();

                await Parallel.ForEachAsync(tradersToProcess, new ParallelOptions 
                { 
                    MaxDegreeOfParallelism = 5,
                    CancellationToken = stoppingToken 
                }, async (t, ct) =>
                {
                    try
                    {
                        using var scope = _services.CreateScope();
                        var traderRepo = scope.ServiceProvider.GetRequiredService<ITraderRepository>();
                        var tradeRepo = scope.ServiceProvider.GetRequiredService<ITradeRepository>();
                        var marketRepo = scope.ServiceProvider.GetRequiredService<IMarketRepository>();
                        var providers = scope.ServiceProvider.GetRequiredService<IEnumerable<IPlatformProvider>>();
                        var rateLimiter = scope.ServiceProvider.GetRequiredService<IRateLimiter>();

                        var trader = await traderRepo.GetByIdAsync(t.Id, ct);
                        if (trader == null) return;

                        var provider = providers.FirstOrDefault(p => p.Platform == trader.Platform && p.IsImplemented);
                        if (provider == null) return;

                        var config = scope.ServiceProvider.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
                        bool isEnabled = config.GetValue<bool>($"PlatformSettings:{provider.PlatformName}:EnableCrawling", provider.Platform == PlatformType.Polymarket);
                        if (!isEnabled)
                        {
                            trader.LastPolledAt = DateTime.UtcNow;
                            await traderRepo.UpdateAsync(trader, ct);
                            return;
                        }

                        using var platformCtx = PlatformLogContext.Push(provider.PlatformName);
                        await rateLimiter.WaitAsync(trader.Platform, ct);

                        bool isDeepResync = activeJob != null && activeJob.JobType == Predictalytics.Domain.Enums.JobType.DeepResync;
                        bool isInitial = !trader.IsInitialImportComplete || isDeepResync;
                        
                        bool isWeeklyBiopsy = trader.IngestMode == IngestMode.SnapshotOnly && (!trader.LastTradesUpdatedAt.HasValue || (DateTime.UtcNow - trader.LastTradesUpdatedAt.Value).TotalDays >= 7);
                        bool skipTradeFetch = trader.IngestMode == IngestMode.SnapshotOnly && !isWeeklyBiopsy && !isDeepResync;
                        
                        _logger.LogInformation("{Trader}: Starting {Type} sync (Mode: {Mode})", trader.DisplayName, isDeepResync ? "DEEP RESYNC" : (isInitial ? "INITIAL FULL" : "INCREMENTAL"), trader.IngestMode);

                        if (isDeepResync)
                        {
                            var db = scope.ServiceProvider.GetRequiredService<Predictalytics.Infrastructure.Data.AppDbContext>();
                            await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, "DELETE FROM Trades WHERE TraderId = {0} AND PlatformTradeId LIKE 'COMPACT_%'", t.Id);
                            await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, "DELETE FROM Trades WHERE TraderId = {0} AND PlatformTradeId LIKE 'AGG_%'", t.Id);
                            await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, "DELETE FROM TraderPositions WHERE TraderId = {0}", t.Id);
                        }
                        
                        IReadOnlyList<Domain.Entities.Trade> fetchedTrades = new List<Domain.Entities.Trade>();
                        
                        if (!skipTradeFetch)
                        {
                            if (isDeepResync || isWeeklyBiopsy)
                            {
                                fetchedTrades = await provider.GetTradesPagedAsync(trader.PlatformUserId, 500, ct);
                            }
                            else
                            {
                                fetchedTrades = await provider.GetTraderTradesAsync(trader.PlatformUserId, TradesPerFetch, ct);
                            }
                            
                            var validTrades = fetchedTrades.Where(tr => !string.IsNullOrWhiteSpace(tr.PlatformTradeId)).ToList();
    
                            var updatedName = validTrades.FirstOrDefault(t => !string.IsNullOrEmpty(t.TransientDisplayName))?.TransientDisplayName;
                            if (!string.IsNullOrEmpty(updatedName) && !string.Equals(trader.DisplayName, updatedName, StringComparison.OrdinalIgnoreCase))
                            {
                                trader.DisplayName = updatedName;
                                await traderRepo.UpdateAsync(trader, ct);
                            }
                            
                            // Classification (IngestMode)
                            var last500 = validTrades.OrderByDescending(t => t.ExecutedAt).Take(500).ToList();
                            if (last500.Count >= 50)
                            {
                                var minDate = last500.Min(x => x.ExecutedAt);
                                var maxDate = last500.Max(x => x.ExecutedAt);
                                var days = (maxDate - minDate).TotalDays;
                                if (days > 0.01)
                                {
                                    var tradesPerDay = last500.Count / days;
                                    var newMode = trader.IngestMode;
                                    
                                    if (tradesPerDay > 5000) newMode = IngestMode.SnapshotOnly;
                                    else if (tradesPerDay > 100 && trader.IngestMode == IngestMode.Full) newMode = IngestMode.Aggregated;
                                    else if (trader.IngestMode == IngestMode.SnapshotOnly && tradesPerDay < 2500) newMode = IngestMode.Aggregated;
                                    else if (trader.IngestMode == IngestMode.Aggregated && tradesPerDay < 50 && days >= 7) newMode = IngestMode.Full;
                                    
                                    if (newMode != trader.IngestMode)
                                    {
                                        _logger.LogInformation("{Trader}: IngestMode changing from {Old} to {New} (Trades/Day: {TPD:F1})", trader.DisplayName, trader.IngestMode, newMode, tradesPerDay);
                                        trader.IngestMode = newMode;
                                        await traderRepo.UpdateAsync(trader, ct);
                                    }
                                }
                            }
                            
                            // fetchedTrades are kept for weekly biopsy resolving, we will clear newTrades afterwards.
                        }

                        var newTrades = new List<Domain.Entities.Trade>();
                        if (fetchedTrades.Count > 0)
                        {
                            var validTrades = fetchedTrades.Where(tr => !string.IsNullOrWhiteSpace(tr.PlatformTradeId)).ToList();
                            var fetchedTradeIds = validTrades.Select(tr => tr.PlatformTradeId).ToList();
                            var knownTradeIds = await tradeRepo.GetKnownPlatformTradeIdsAsync(trader.Platform, trader.Id, fetchedTradeIds, ct);
                            
                            // Collect all unique AssetIds we might need to resolve
                            var assetIdsToResolve = validTrades
                                .Where(tr => !knownTradeIds.Contains(tr.PlatformTradeId) || isInitial)
                                .Select(tr => tr.AssetId)
                                .Where(id => !string.IsNullOrEmpty(id))
                                .Distinct()
                                .ToList();
    
                            // Pre-fill local cache with bulk query
                            var missingAssetIds = assetIdsToResolve.Where(id => !outcomeCache.ContainsKey(id!)).ToList();
                            if (missingAssetIds.Count > 0)
                            {
                                var resolvedOutcomes = await marketRepo.GetOutcomesByTokenIdsAsync(missingAssetIds!, ct);
                                foreach (var o in resolvedOutcomes)
                                {
                                    outcomeCache.TryAdd(o.TokenId, o);
                                }
                            }
    
                            foreach (var trade in validTrades)
                            {
                                if (knownTradeIds.Contains(trade.PlatformTradeId))
                                {
                                    continue;
                                }
    
                                trade.TraderId = trader.Id;
                                
                                if (!string.IsNullOrEmpty(trade.AssetId))
                                {
                                    if (outcomeCache.TryGetValue(trade.AssetId, out var outcome))
                                    {
                                        trade.MarketOutcomeId = outcome.Id;
                                        trade.Outcome = outcome.Label;
                                        if (outcome.Market != null)
                                            trade.DbMarketId = outcome.Market.Id;
                                    }
                                    else if (!string.IsNullOrEmpty(trade.MarketId))
                                    {
                                        // Fallback for missing outcomes: try to fetch market
                                        var marketTask = marketFetchCache.GetOrAdd(trade.MarketId, _ => provider.GetMarketAsync(trade.MarketId, ct));
                                        var newMarket = await marketTask;
                                        if (newMarket != null) 
                                        { 
                                            await marketRepo.AddOrUpdateAsync(newMarket, ct); 
                                            var newOutcome = await marketRepo.GetOutcomeByTokenIdAsync(trade.AssetId, ct);
                                            if (newOutcome != null)
                                            {
                                                outcomeCache.TryAdd(trade.AssetId, newOutcome);
                                                trade.MarketOutcomeId = newOutcome.Id;
                                                trade.Outcome = newOutcome.Label;
                                                if (newOutcome.Market != null)
                                                    trade.DbMarketId = newOutcome.Market.Id;
                                            }
                                        }
                                    }
                                }
                                newTrades.Add(trade);
                            }
                        }

                        if (trader.IngestMode == IngestMode.Aggregated && newTrades.Count > 0)
                        {
                            // Only completed hours are aggregated+persisted; the current (growing)
                            // hour is deferred so aggregate rows stay immutable for the checkpoint
                            // engine. See TradeAggregation.AggregateCompletedHours.
                            newTrades = Predictalytics.Application.Services.TradeAggregation
                                .AggregateCompletedHours(newTrades, trader.Id, DateTime.UtcNow);
                        }

                        if (isWeeklyBiopsy && newTrades.Count > 0)
                        {
                            var db = scope.ServiceProvider.GetRequiredService<Predictalytics.Infrastructure.Data.AppDbContext>();
                            var positions = await db.TraderPositions
                                .Include(p => p.MarketOutcome).ThenInclude(o => o.Market)
                                .Where(p => p.TraderId == trader.Id)
                                .ToListAsync(ct);

                            var computedTraits = Predictalytics.Application.Services.TraderTraitCalculator.Compute(trader, newTrades, positions);

                            db.TraderTraits.RemoveRange(db.TraderTraits.Where(tt => tt.TraderId == trader.Id));
                            foreach (var (traitName, value) in computedTraits)
                            {
                                db.TraderTraits.Add(new Predictalytics.Domain.Entities.TraderTrait { TraderId = trader.Id, Trait = traitName, Value = value });
                            }
                            await db.SaveChangesAsync(ct);
                            
                            newTrades.Clear(); // DO NOT persist trades!
                        }

                        if (trader.IngestMode == IngestMode.SnapshotOnly)
                        {
                            // "SnapshotOnly (Tier C): Stündlich: GetTraderPositionsAsync -> TraderPositions upserten"
                            if (!trader.LastTradesUpdatedAt.HasValue || (DateTime.UtcNow - trader.LastTradesUpdatedAt.Value).TotalHours >= 1)
                            {
                                try 
                                {
                                    var positions = await provider.GetTraderPositionsAsync(trader.PlatformUserId, ct);
                                    decimal totalRealizedPnl = 0;
                                    decimal totalUnrealizedPnl = 0;

                                    if (positions != null && positions.Count > 0)
                                    {
                                        var db = scope.ServiceProvider.GetRequiredService<Predictalytics.Infrastructure.Data.AppDbContext>();
                                        var existingPos = db.TraderPositions.Where(tp => tp.TraderId == trader.Id).ToList();
                                        foreach(var info in positions)
                                        {
                                            int? marketOutcomeId = null;
                                            if (!string.IsNullOrEmpty(info.AssetId))
                                            {
                                                var outcome = await marketRepo.GetOutcomeByTokenIdAsync(info.AssetId, ct);
                                                if (outcome != null)
                                                {
                                                    marketOutcomeId = outcome.Id;
                                                }
                                            }
                                            
                                            if (!marketOutcomeId.HasValue) continue;
                                            
                                            var ex = existingPos.FirstOrDefault(ep => ep.MarketOutcomeId == marketOutcomeId.Value);
                                            if (ex != null)
                                            {
                                                ex.SharesHeld = info.Size;
                                                ex.AvgCost = info.AveragePrice;
                                                ex.RealizedPnl = info.RealizedPnl; // Mapped cashPnl!
                                            }
                                            else
                                            {
                                                ex = new Predictalytics.Domain.Entities.TraderPosition
                                                {
                                                    TraderId = trader.Id,
                                                    MarketOutcomeId = marketOutcomeId.Value,
                                                    SharesHeld = info.Size,
                                                    AvgCost = info.AveragePrice,
                                                    RealizedPnl = info.RealizedPnl
                                                };
                                                db.TraderPositions.Add(ex);
                                            }

                                            // Calculate unrealized PnL: pos.SharesHeld * (currentPrice - pos.AvgCost)
                                            if (!string.IsNullOrEmpty(info.AssetId))
                                            {
                                                var outcomeObj = await marketRepo.GetOutcomeByTokenIdAsync(info.AssetId, ct);
                                                if (outcomeObj != null)
                                                {
                                                    var unrealized = ex.SharesHeld * (outcomeObj.CurrentPrice - ex.AvgCost);
                                                    totalUnrealizedPnl += unrealized;
                                                }
                                            }
                                            totalRealizedPnl += ex.RealizedPnl;
                                        }
                                        await db.SaveChangesAsync(ct);
                                    }

                                    // Overall PnL
                                    decimal overallPnl = totalRealizedPnl + totalUnrealizedPnl;

                                    // Let's query Leaderboard PnLs
                                    decimal pnl30d = 0;
                                    decimal pnl7d = 0;
                                    decimal pnl24h = 0;

                                    var polyApi = scope.ServiceProvider.GetService<Predictalytics.Infrastructure.Providers.Polymarket.PolymarketApiClient>();
                                    if (polyApi != null)
                                    {
                                        try
                                        {
                                            var leaderboardAll = await polyApi.GetLeaderboardAsync(limit: 50, timePeriod: "ALL", ct: ct);
                                            var entryAll = leaderboardAll.FirstOrDefault(e => string.Equals(e.ProxyWallet, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase) || string.Equals(e.UserName, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase));
                                            if (entryAll != null)
                                            {
                                                overallPnl = (decimal)entryAll.Pnl;
                                            }

                                            var leaderboard30d = await polyApi.GetLeaderboardAsync(limit: 50, timePeriod: "30D", ct: ct);
                                            var entry30d = leaderboard30d.FirstOrDefault(e => string.Equals(e.ProxyWallet, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase) || string.Equals(e.UserName, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase));
                                            if (entry30d != null)
                                            {
                                                pnl30d = (decimal)entry30d.Pnl;
                                            }

                                            var leaderboard7d = await polyApi.GetLeaderboardAsync(limit: 50, timePeriod: "7D", ct: ct);
                                            var entry7d = leaderboard7d.FirstOrDefault(e => string.Equals(e.ProxyWallet, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase) || string.Equals(e.UserName, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase));
                                            if (entry7d != null)
                                            {
                                                pnl7d = (decimal)entry7d.Pnl;
                                            }

                                            var leaderboard24h = await polyApi.GetLeaderboardAsync(limit: 50, timePeriod: "24H", ct: ct);
                                            var entry24h = leaderboard24h.FirstOrDefault(e => string.Equals(e.ProxyWallet, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase) || string.Equals(e.UserName, trader.PlatformUserId, StringComparison.OrdinalIgnoreCase));
                                            if (entry24h != null)
                                            {
                                                pnl24h = (decimal)entry24h.Pnl;
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            _logger.LogWarning(ex, "Failed to fetch leaderboard PnLs for SnapshotOnly trader {TraderId}", trader.Id);
                                        }
                                    }

                                    var dbCtx = scope.ServiceProvider.GetRequiredService<Predictalytics.Infrastructure.Data.AppDbContext>();
                                    var analyticsObj = await dbCtx.TraderAnalytics.FirstOrDefaultAsync(a => a.TraderId == trader.Id, ct);
                                    if (analyticsObj == null)
                                    {
                                        analyticsObj = new Predictalytics.Domain.Entities.TraderAnalytics { TraderId = trader.Id };
                                        dbCtx.TraderAnalytics.Add(analyticsObj);
                                    }

                                    analyticsObj.OverallPnL = overallPnl;
                                    analyticsObj.PnL30d = pnl30d;
                                    analyticsObj.PnL7d = pnl7d;
                                    analyticsObj.PnL24h = pnl24h;
                                    analyticsObj.LastCalculatedAt = DateTime.UtcNow;

                                    trader.TotalPnl = overallPnl;
                                    trader.LastAnalyzedAt = DateTime.UtcNow;

                                    // Save Daily Snapshot (Equity curve)
                                    var today = DateTime.UtcNow.Date;
                                    var snapshot = await dbCtx.TraderDailySnapshots.FirstOrDefaultAsync(s => s.TraderId == trader.Id && s.Date == today, ct);
                                    if (snapshot == null)
                                    {
                                        dbCtx.TraderDailySnapshots.Add(new Predictalytics.Domain.Entities.TraderDailySnapshot
                                        {
                                            TraderId = trader.Id,
                                            Date = today,
                                            TotalPnl = overallPnl,
                                            CurrentBalance = analyticsObj.CurrentBalance
                                        });
                                    }
                                    else
                                    {
                                        snapshot.TotalPnl = overallPnl;
                                    }

                                    await dbCtx.SaveChangesAsync(ct);
                                }
                                catch (Exception ex)
                                {
                                    _logger.LogWarning(ex, "Failed to fetch positions / leaderboard for SnapshotOnly trader {TraderId}", trader.Id);
                                }
                            }
                        }

                        if (newTrades.Count > 0)
                        {
                            var uniqueNewTrades = newTrades.GroupBy(tr => tr.PlatformTradeId, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
                            try {
                                await tradeRepo.AddRangeAsync(uniqueNewTrades, ct);
                                _statsService.TrackTradeActivity(trader.Platform, uniqueNewTrades.Count);
                                trader.TotalTrades += uniqueNewTrades.Count;
                                _logger.LogInformation("{Trader}: {New} new trades imported", trader.DisplayName, uniqueNewTrades.Count);

                                // Fable 5 recommendation: fetch /book directly after recent trades to get market overview
                                var recentTrades = uniqueNewTrades.Where(t => (DateTime.UtcNow - t.ExecutedAt).TotalMinutes < 5).ToList();
                                foreach (var rt in recentTrades.GroupBy(t => new { t.MarketOutcomeId, t.AssetId }))
                                {
                                    if (rt.Key.MarketOutcomeId.HasValue && rt.Key.MarketOutcomeId.Value > 0 && !string.IsNullOrEmpty(rt.Key.AssetId) && provider is Predictalytics.Infrastructure.Providers.Polymarket.PolymarketProvider polyProv)
                                    {
                                        try
                                        {
                                            var polyApi = scope.ServiceProvider.GetService<Predictalytics.Infrastructure.Providers.Polymarket.PolymarketApiClient>();
                                            if (polyApi != null)
                                            {
                                                var book = await polyApi.GetOrderBookAsync(rt.Key.AssetId, ct);
                                                if (book != null && book.Bids.Count > 0 && book.Asks.Count > 0)
                                                {
                                                    decimal topBid = decimal.Parse(book.Bids[0].Price, System.Globalization.CultureInfo.InvariantCulture);
                                                    decimal topAsk = decimal.Parse(book.Asks[0].Price, System.Globalization.CultureInfo.InvariantCulture);
                                                    decimal midPrice = (topBid + topAsk) / 2m;

                                                    var snapshot = new Domain.Entities.MarketOutcomePriceSnapshot
                                                    {
                                                        MarketOutcomeId = rt.Key.MarketOutcomeId.Value,
                                                        Timestamp = DateTime.UtcNow,
                                                        Price = midPrice
                                                    };
                                                    await marketRepo.SavePriceSnapshotsAsync(rt.Key.MarketOutcomeId.Value, new[] { snapshot }, ct);
                                                }
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            _logger.LogWarning(ex, "Failed to fetch /book for Outcome {OutcomeId}", rt.Key.MarketOutcomeId.Value);
                                        }
                                    }
                                }
                            } catch (Exception ex) when (ex.ToString().Contains("Duplicate entry") || (ex.InnerException?.Message.Contains("Duplicate entry") ?? false)) {
                                _statsService.TrackDuplicateError(trader.Platform, 1);
                            }
                        }

                        trader.IsInitialImportComplete = true;
                        trader.LastTradesUpdatedAt = DateTime.UtcNow;
                        trader.LastPolledAt = DateTime.UtcNow;
                        if (isDeepResync) trader.LastAnalyzedAt = null;
                        await traderRepo.UpdateAsync(trader, ct);

                        if (activeJob != null && activeJob.TraderId == trader.Id)
                        {
                            using var jobScope = _services.CreateScope();
                            var updateJobRepo = jobScope.ServiceProvider.GetRequiredService<IJobRepository>();
                            activeJob.Status = Predictalytics.Domain.Enums.JobStatus.Completed;
                            activeJob.CompletedAt = DateTime.UtcNow;
                            await updateJobRepo.UpdateAsync(activeJob, ct);
                        }
                    }
                    catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { /* App shutting down */ }
                    catch (Exception ex) 
                    { 
                        _logger.LogError(ex, "Error syncing trader {TraderId}", t.Id);
                        if (activeJob != null && activeJob.TraderId == t.Id)
                        {
                            try {
                                using var jobScope = _services.CreateScope();
                                var updateJobRepo = jobScope.ServiceProvider.GetRequiredService<IJobRepository>();
                                activeJob.Status = Predictalytics.Domain.Enums.JobStatus.Failed;
                                activeJob.CompletedAt = DateTime.UtcNow;
                                activeJob.ErrorMessage = ex.Message;
                                await updateJobRepo.UpdateAsync(activeJob, CancellationToken.None);
                            } catch { /* Ignore secondary errors */ }
                        }
                    }
                });
            }
            catch (OperationCanceledException) { break; }
            catch (Exception ex) when (ex.ToString().Contains("MySqlException") || ex.ToString().Contains("Connection"))
            {
                if (DateTime.UtcNow - _lastDbError > TimeSpan.FromMinutes(10))
                {
                    _logger.LogWarning("⚠️ Database connection lost in TradeHistoryWorker. Retrying in 2m. (Error: {Message})", ex.Message);
                    _lastDbError = DateTime.UtcNow;
                }
            }
            catch (Exception ex) { _logger.LogError(ex, "TradeHistoryWorker error"); }

            if (tradersToProcess == null || tradersToProcess.Count == 0)
            {
                await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
            }
        }

        _logger.LogInformation("📜 TradeHistoryWorker stopped");
    }
}
