using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Domain.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Text;

namespace Predictalytics.Infrastructure.Data.Repositories;

public class TraderRepository : ITraderRepository
{
    private readonly AppDbContext _db;
    public TraderRepository(AppDbContext db) => _db = db;

    public async Task<Trader?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        return await _db.Traders
            .Include(t => t.CurrentScore)
            .Include(t => t.Analytics)
            .Include(t => t.CategoryPerformances)
            .Include(t => t.Traits)
            .FirstOrDefaultAsync(t => t.Id == id, ct);
    }

    public async Task<Trader?> GetByPlatformIdAsync(PlatformType platform, string platformUserId, CancellationToken ct = default)
        => await _db.Traders.Include(t => t.CurrentScore).Include(t => t.CategoryPerformances)
            .FirstOrDefaultAsync(t => t.Platform == platform && t.PlatformUserId == platformUserId, ct);

    public async Task<IReadOnlyList<Trader>> GetAllAsync(PlatformType? platform = null, int skip = 0, int take = 50, CancellationToken ct = default)
    {
        var q = _db.Traders
            .Include(t => t.CurrentScore)
            .Include(t => t.Analytics)
            .Include(t => t.Traits)
            .AsQueryable();

        if (platform.HasValue) q = q.Where(t => t.Platform == platform.Value);

        // Sort by CombinedScore, then by PnL as fallback
        return await q.OrderByDescending(t => t.CurrentScore != null ? t.CurrentScore.CombinedScore : 0)
            .ThenByDescending(t => t.TotalPnl)
            .Skip(skip).Take(take).ToListAsync(ct);
    }

    public async Task<IReadOnlyList<Trader>> GetWatchlistedAsync(CancellationToken ct = default)
        => await _db.Traders.Include(t => t.CurrentScore).Include(t => t.WatchlistEntries)
            .Where(t => t.WatchlistEntries.Any()).ToListAsync(ct);

    public async Task<IReadOnlyList<Trader>> GetTopByScoreAsync(int count = 20, CancellationToken ct = default)
        => await _db.Traders.Include(t => t.CurrentScore).Include(t => t.Analytics)
            .OrderByDescending(t => t.CurrentScore!.CombinedScore).Take(count).ToListAsync(ct);

    public async Task<IReadOnlyList<Trader>> GetTopByPnLAsync(int count = 5, DateTime? since = null, CancellationToken ct = default)
    {
        var q = _db.Traders.Include(t => t.CurrentScore).Include(t => t.Analytics).AsQueryable();
        
        // If 'since' is 7 days ago, try to use PnL7d from Analytics
        if (since.HasValue && (DateTime.UtcNow - since.Value).TotalDays >= 6.9)
        {
            return await q.OrderByDescending(t => t.Analytics != null ? t.Analytics.PnL7d : t.TotalPnl)
                .Take(count).ToListAsync(ct);
        }

        return await q.OrderByDescending(t => t.TotalPnl).Take(count).ToListAsync(ct);
    }

    public async Task<int> GetCountAsync(PlatformType? platform = null, CancellationToken ct = default)
    {
        var q = _db.Traders.AsQueryable();
        if (platform.HasValue) q = q.Where(t => t.Platform == platform.Value);
        return await q.CountAsync(ct);
    }

    public async Task AddAsync(Trader trader, CancellationToken ct = default)
    { _db.Traders.Add(trader); await _db.SaveChangesAsync(ct); }

    public async Task UpdateAsync(Trader trader, CancellationToken ct = default)
    {
        _db.Traders.Update(trader);
        await _db.SaveChangesAsync(ct);
    }

    public async Task UpdateRanksAsync(IEnumerable<(int TraderId, int Rank)> ranks, CancellationToken ct = default)
    {
        // Batch update ranks using raw SQL to avoid N+1 and loading entities
        var sql = new StringBuilder();
        sql.AppendLine("UPDATE TraderScores SET Rank = CASE TraderId");
        var ids = new List<int>();
        foreach (var r in ranks)
        {
            sql.AppendLine($"WHEN {r.TraderId} THEN {r.Rank}");
            ids.Add(r.TraderId);
        }
        sql.AppendLine("ELSE Rank END WHERE TraderId IN (" + string.Join(",", ids) + ");");
        
        if (ids.Count > 0)
        {
            await _db.Database.ExecuteSqlRawAsync(sql.ToString(), ct);
        }
    }

    public async Task DeleteAsync(int id, CancellationToken ct = default)
    {
        var t = await _db.Traders.FindAsync(new object[] { id }, ct);
        if (t != null) { _db.Traders.Remove(t); await _db.SaveChangesAsync(ct); }
    }

    public async Task<IReadOnlyList<Trader>> GetTradersDueForTradeUpdateAsync(int cooldownHours = 12, int take = 20, CancellationToken ct = default)
    {
        var normalCutoff = DateTime.UtcNow.AddHours(-cooldownHours);
        var priorityCutoff = DateTime.UtcNow.AddHours(-1); // Sync priority traders more often, but not continuously
        
        return await _db.Traders
            .Include(t => t.WatchlistEntries)
            .Where(t => t.LastTradesUpdatedAt == null || 
                        (!t.IsInitialImportComplete) || 
                        ((!t.IsAutoDiscovered || t.WatchlistEntries.Any()) && t.LastTradesUpdatedAt < priorityCutoff) ||
                        (t.IsAutoDiscovered && t.LastTradesUpdatedAt < normalCutoff))
            .OrderBy(t => t.IsAutoDiscovered) // Manual first (false = 0)
            .ThenByDescending(t => t.WatchlistEntries.Any()) // Watchlisted next (true = 1)
            .ThenBy(t => t.IsInitialImportComplete) // New ones next (false = 0)
            .ThenBy(t => t.LastTradesUpdatedAt ?? DateTime.MinValue) // Oldest first
            .Take(take)
            .ToListAsync(ct);
    }

    public async Task<IReadOnlyList<Trader>> GetTradersForCleanupAsync(DateTime inactiveSince, DateTime errorSince, int take = 50, CancellationToken ct = default)
    {
        return await _db.Traders
            .Where(t => t.IsAutoDiscovered && !t.WatchlistEntries.Any())
            .Where(t => (t.LastPolledAt != null && t.LastPolledAt < inactiveSince) ||
                        (t.LastApiErrorAt != null && t.LastApiErrorAt < errorSince))
            .OrderBy(t => t.LastApiErrorAt ?? DateTime.MaxValue) // Prioritize errors first
            .Take(take)
            .ToListAsync(ct);
    }

    public async Task<IReadOnlyList<Trader>> GetTradersForPollingAsync(int take, CancellationToken ct = default)
    {
        return await _db.Traders
            .OrderBy(t => t.LastPolledAt)
            .Take(take)
            .ToListAsync(ct);
    }

    public async Task<IReadOnlyList<Trader>> SearchAsync(string query, int take = 20, CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(query)) return Array.Empty<Trader>();

        return await _db.Traders
            .Include(t => t.CurrentScore)
            .Include(t => t.Analytics)
            .Where(t => t.DisplayName.Contains(query) || 
                        t.PlatformUserId.Contains(query) || 
                        t.Id.ToString() == query)
            .OrderByDescending(t => t.CurrentScore != null ? t.CurrentScore.CombinedScore : 0)
            .ThenByDescending(t => t.TotalPnl)
            .Take(take)
            .ToListAsync(ct);
    }

    public async Task<IReadOnlyList<TraderPosition>> GetPositionsAsync(int traderId, CancellationToken ct = default)
    {
        return await _db.TraderPositions
            .Include(p => p.MarketOutcome)
            .ThenInclude(o => o.Market)
            .Where(p => p.TraderId == traderId && (p.SharesHeld > 0 || p.RealizedPnl != 0))
            .OrderByDescending(p => p.LastTradeExecutedAt ?? DateTime.MinValue)
            .ToListAsync(ct);
    }
}
