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

namespace Predictalytics.Worker.Services;

/// <summary>
/// Background service that retroactively resolves missing MarketOutcomeIds for trades.
/// Scans for trades with NULL MarketOutcomeId and attempts to link them via AssetId.
/// </summary>
public class TradeReconciliationWorker : BackgroundService
{
    private readonly IServiceProvider _services;
    private readonly ILogger<TradeReconciliationWorker> _logger;

    private const int BatchSize = 250;
    private const int IntervalSeconds = 30;

    public TradeReconciliationWorker(IServiceProvider services, ILogger<TradeReconciliationWorker> logger)
    {
        _services = services;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("🛠️ TradeReconciliationWorker started (batch: {Batch}, interval: {Sec}s)", BatchSize, IntervalSeconds);
        
        // Initial delay to let other workers settle
        await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                int reconciledCount = await ReconcileBatchAsync(stoppingToken);
                
                if (reconciledCount > 0)
                {
                    _logger.LogInformation("✅ Reconciled {Count} orphaned trades", reconciledCount);
                }
                else
                {
                    _logger.LogDebug("🛠️ No orphaned trades found for reconciliation");
                }
            }
            catch (OperationCanceledException) { break; }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in TradeReconciliationWorker cycle");
            }

            await Task.Delay(TimeSpan.FromSeconds(IntervalSeconds), stoppingToken);
        }

        _logger.LogInformation("🛠️ TradeReconciliationWorker stopped");
    }

    private async Task<int> ReconcileBatchAsync(CancellationToken ct)
    {
        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<Predictalytics.Infrastructure.Data.AppDbContext>();

        // Bulk update orphaned trades
        int reconciledCount = 0;
        try
        {
            // 1. Fetch missing markets on demand
            var missingMarketIds = await db.Trades
                .Where(t => t.MarketOutcomeId == null && !string.IsNullOrEmpty(t.MarketId) && !db.Markets.Any(m => m.ConditionId == t.MarketId))
                .Select(t => t.MarketId)
                .Distinct()
                .OrderBy(x => x)
                .Take(50) // Limit to avoid rate limits
                .ToListAsync(ct);

            if (missingMarketIds.Count > 0)
            {
                var platformProviders = scope.ServiceProvider.GetRequiredService<IEnumerable<IPlatformProvider>>();
                var polyProvider = platformProviders.FirstOrDefault(p => p.Platform == Predictalytics.Domain.Enums.PlatformType.Polymarket);
                var marketRepo = scope.ServiceProvider.GetRequiredService<IMarketRepository>();
                
                if (polyProvider != null)
                {
                    var newEvents = new List<Predictalytics.Domain.Entities.Event>();
                    foreach (var marketId in missingMarketIds)
                    {
                        try
                        {
                            var market = await polyProvider.GetMarketAsync(marketId, ct);
                            if (market != null && market.Event != null)
                            {
                                newEvents.Add(market.Event);
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "Failed to fetch market {MarketId} during reconciliation", marketId);
                        }
                    }
                    if (newEvents.Count > 0)
                    {
                        await marketRepo.AddOrUpdateEventsAsync(newEvents, ct);
                    }
                }
            }

            // 2. Bulk update orphaned trades
            var pairsToReset = await db.Trades
                .Where(t => t.MarketOutcomeId == null && t.AssetId != "")
                .Join(db.MarketOutcomes, t => t.AssetId, o => o.TokenId, (t, o) => new { t.TraderId, MarketOutcomeId = o.Id })
                .Distinct()
                .ToListAsync(ct);

            int totalReconciled = 0;
            int batchAffected;
            do
            {
                batchAffected = await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(db.Database, @"
                    UPDATE Trades t
                    JOIN (
                        SELECT t2.Id, o.Id AS OutcomeId, o.Label, m.Id AS MarketDbId
                        FROM Trades t2
                        JOIN MarketOutcomes o ON t2.AssetId = o.TokenId
                        JOIN Markets m ON o.MarketId = m.Id
                        WHERE t2.MarketOutcomeId IS NULL AND t2.AssetId != ''
                        LIMIT 5000
                    ) x ON t.Id = x.Id
                    SET t.MarketOutcomeId = x.OutcomeId, t.Outcome = x.Label, t.DbMarketId = x.MarketDbId;
                ", ct);
                
                totalReconciled += batchAffected;
                if (batchAffected > 0) await Task.Delay(100, ct);
            } while (batchAffected > 0);
            reconciledCount = totalReconciled;

            if (reconciledCount > 0 && pairsToReset.Any())
            {
                // Reset checkpoints for positions that received newly linked older trades
                var posIdsToReset = await db.TraderPositions
                    .Where(tp => pairsToReset.Select(p => p.TraderId).Contains(tp.TraderId) && 
                                 pairsToReset.Select(p => p.MarketOutcomeId).Contains(tp.MarketOutcomeId))
                    .Select(tp => tp.Id)
                    .ToListAsync(ct);

                // Need to filter client side since EF core can't translate tuple Contains
                var actualPosIdsToReset = (await db.TraderPositions
                    .Where(tp => posIdsToReset.Contains(tp.Id))
                    .ToListAsync(ct))
                    .Where(tp => pairsToReset.Any(p => p.TraderId == tp.TraderId && p.MarketOutcomeId == tp.MarketOutcomeId))
                    .Select(tp => tp.Id)
                    .ToList();

                if (actualPosIdsToReset.Any())
                {
                    var positionsToUpdate = await db.TraderPositions
                        .Where(tp => actualPosIdsToReset.Contains(tp.Id))
                        .ToListAsync(ct);

                    var prunedPositions = positionsToUpdate.Where(p => p.IsHistoryPruned).ToList();
                    if (prunedPositions.Any())
                    {
                        _logger.LogWarning("Skipping checkpoint reset for {Count} positions because their history is pruned. Orphan trades linked to these positions will not be applied to PnL.", prunedPositions.Count);
                    }

                    var safePosIds = positionsToUpdate.Where(p => !p.IsHistoryPruned).Select(p => p.Id).ToList();

                    if (safePosIds.Any())
                    {
                        await db.TraderPositions
                            .Where(tp => safePosIds.Contains(tp.Id))
                            .ExecuteUpdateAsync(s => s.SetProperty(p => p.LastAppliedTradeId, 0), ct);
                    }
                }

                // Force re-analysis of traders who now have new linked trades
                var traderIds = pairsToReset.Select(p => p.TraderId).Distinct().ToList();
                await db.Traders
                    .Where(t => traderIds.Contains(t.Id))
                    .ExecuteUpdateAsync(s => s.SetProperty(p => p.LastAnalyzedAt, (DateTime?)null), ct);
            }
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            _logger.LogInformation("Cancellation requested during reconciliation, stopping batch.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error executing bulk reconciliation SQL");
        }

        return reconciledCount;
    }
}
