using Predictalytics.Application.Interfaces;
using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Domain.Interfaces;
using Predictalytics.Domain.ValueObjects;
using Microsoft.Extensions.Logging;

namespace Predictalytics.Application.Services;

/// <summary>
/// Calculates priority and quality scores for traders based on their activity,
/// performance, and trading patterns.
/// </summary>
public class ScoringService : IScoringService
{
    private readonly ITraderRepository _traderRepo;
    private readonly ITradeRepository _tradeRepo;
    private readonly ILogger<ScoringService> _logger;

    // Scoring weights (configurable in future)
    private const decimal ActivityWeight = 0.25m;
    private const decimal QualityWeight = 0.35m;
    private const decimal VolumeWeight = 0.20m;
    private const decimal TimingWeight = 0.20m;

    public ScoringService(ITraderRepository traderRepo, ITradeRepository tradeRepo, ILogger<ScoringService> logger)
    {
        _traderRepo = traderRepo;
        _tradeRepo = tradeRepo;
        _logger = logger;
    }

    public async Task<PriorityScore> CalculateScoreAsync(int traderId, CancellationToken ct = default)
    {
        var trader = await _traderRepo.GetByIdAsync(traderId, ct);
        if (trader == null)
        {
            _logger.LogWarning("Cannot score trader {TraderId}: not found", traderId);
            return new PriorityScore(0, 0, 0, 0, 0);
        }

        var trades = await _tradeRepo.GetByTraderIdAsync(traderId, 0, 200, ct);

        // Activity Score: based on trade frequency and recency
        var activityScore = CalculateActivityScore(trades);

        // Quality Score: based on win rate and PnL
        var qualityScore = CalculateQualityScore(trader);

        // Volume Score: based on average trade size
        var volumeScore = CalculateVolumeScore(trades);

        // Timing Score: based on entry/exit timing quality
        var timingScore = CalculateTimingScore(trades);

        // Combined weighted score
        var combined = Math.Round(
            activityScore * ActivityWeight +
            qualityScore * QualityWeight +
            volumeScore * VolumeWeight +
            timingScore * TimingWeight, 2);

        // Basic Strategy and Bot Classification
        var avgSize = trades.Count > 0 ? trades.Average(t => t.Amount) : 0;
        var botIndicators = new List<string>();
        var times = trades.Select(t => t.ExecutedAt).OrderBy(t => t).ToList();
        if (times.Count > 10)
        {
            var intervals = times.Zip(times.Skip(1), (a, b) => (b - a).TotalSeconds).ToList();
            if (intervals.Average() < 10) botIndicators.Add("Sub-10s trade frequency");
        }

        bool aiControlsStrategy = trader.AiStrategyUpdatedAt.HasValue && trader.AiStrategyUpdatedAt.Value > DateTime.UtcNow.AddDays(-7);
        
        if (!aiControlsStrategy)
        {
            trader.IsSuspectedBot = botIndicators.Count > 0;
            
            var marketKeys = trades
                .Select(t => t.DbMarketId.HasValue ? t.DbMarketId.Value.ToString() : t.MarketId)
                .Where(k => !string.IsNullOrEmpty(k))
                .ToList();
            var marketsTraded = marketKeys.Distinct().Count();
            var hedgeGroups = trades
                .GroupBy(t => t.DbMarketId.HasValue ? t.DbMarketId.Value.ToString() : t.MarketId)
                .Where(g => !string.IsNullOrEmpty(g.Key) && g.Select(t => t.Outcome).Distinct().Count() > 1);
            var hedgingRate = marketsTraded > 0 ? (decimal)hedgeGroups.Count() / marketsTraded * 100 : 0;
            
            trader.Strategy = avgSize > 10000 ? Predictalytics.Domain.Enums.StrategyType.Whale : 
                              hedgingRate > 30 ? Predictalytics.Domain.Enums.StrategyType.Hedger :
                              trader.IsSuspectedBot ? Predictalytics.Domain.Enums.StrategyType.Bot : 
                              Predictalytics.Domain.Enums.StrategyType.Unknown;
        }

        var score = new PriorityScore(activityScore, qualityScore, volumeScore, timingScore, combined, trader.ManualPriorityOverride);

        // Persist score
        var traderScore = trader.CurrentScore ?? new TraderScore { TraderId = traderId };
        traderScore.ActivityScore = activityScore;
        // traderScore.QualityScore is now calculated by CopytradingEstimator
        traderScore.VolumeScore = volumeScore;
        traderScore.TimingScore = timingScore;
        traderScore.CombinedScore = combined;
        // traderScore.CopytradingScore is now calculated by CopytradingEstimator
        traderScore.CalculatedAt = DateTime.UtcNow;

        trader.CurrentScore = traderScore;
        trader.Tier = score.DetermineTier();
        await _traderRepo.UpdateAsync(trader, ct);

        _logger.LogInformation("Scored trader {TraderName} ({TraderId}): Combined={Score}, Tier={Tier}",
            trader.DisplayName, traderId, combined, trader.Tier);

        return score;
    }

    public async Task RecalculateAllScoresAsync(CancellationToken ct = default)
    {
        var traders = await _traderRepo.GetAllAsync(take: 1000, ct: ct);
        _logger.LogInformation("Recalculating scores for {Count} traders", traders.Count);

        var scored = new List<(int TraderId, decimal Score)>();

        foreach (var trader in traders)
        {
            if (ct.IsCancellationRequested) break;

            var needsScoring = trader.CurrentScore == null || 
                               trader.LastTradesUpdatedAt == null || 
                               trader.CurrentScore.CalculatedAt < trader.LastTradesUpdatedAt;

            if (needsScoring)
            {
                var score = await CalculateScoreAsync(trader.Id, ct);
                scored.Add((trader.Id, score.EffectiveScore));
            }
            else
            {
                scored.Add((trader.Id, trader.CurrentScore!.CombinedScore));
            }
        }

        // Update ranks (optimized to avoid N+1 and loading 5000 entities)
        var rankedList = scored.OrderByDescending(s => s.Score).ToList();
        
        await _traderRepo.UpdateRanksAsync(rankedList.Select((x, i) => (x.TraderId, Rank: i + 1)).ToList(), ct);

        _logger.LogInformation("Score recalculation complete. Ranked {Count} traders.", scored.Count);
    }

    public async Task SetManualOverrideAsync(int traderId, int? score, CancellationToken ct = default)
    {
        var trader = await _traderRepo.GetByIdAsync(traderId, ct);
        if (trader == null) return;

        trader.ManualPriorityOverride = score;
        await _traderRepo.UpdateAsync(trader, ct);
        _logger.LogInformation("Set manual priority override for {TraderName}: {Score}", trader.DisplayName, score?.ToString() ?? "cleared");
    }

    private static decimal CalculateActivityScore(IReadOnlyList<Trade> trades)
    {
        if (trades.Count == 0) return 0;

        var now = DateTime.UtcNow;
        var recentTrades = trades.Where(t => t.ExecutedAt > now.AddDays(-7)).ToList();
        var frequencyScore = Math.Min(recentTrades.Count / 10.0m, 1.0m) * 50;

        // Recency bonus
        var lastTrade = trades.MaxBy(t => t.ExecutedAt);
        var hoursSinceLast = (decimal)(now - (lastTrade?.ExecutedAt ?? now.AddDays(-30))).TotalHours;
        var recencyScore = Math.Max(0, 50 - hoursSinceLast / 2);

        return Math.Min(Math.Round(frequencyScore + recencyScore, 2), 100);
    }

    private static decimal CalculateQualityScore(Trader trader)
    {
        // Win rate contribution (0-60 points)
        var winRateScore = trader.WinRate * 0.6m;

        // PnL contribution (0-40 points) - logarithmic scale
        var pnlScore = trader.TotalPnl > 0
            ? Math.Min((decimal)Math.Log10((double)trader.TotalPnl + 1) * 10, 40)
            : 0;

        return Math.Min(Math.Round(winRateScore + pnlScore, 2), 100);
    }

    private static decimal CalculateVolumeScore(IReadOnlyList<Trade> trades)
    {
        if (trades.Count == 0) return 0;

        var avgAmount = trades.Average(t => t.Amount);
        // Score based on average trade size (logarithmic)
        var score = (decimal)Math.Log10((double)avgAmount + 1) * 25;
        return Math.Min(Math.Round(score, 2), 100);
    }

    private static decimal CalculateTimingScore(IReadOnlyList<Trade> trades)
    {
        if (trades.Count < 2) return 50; // neutral if insufficient data

        // Simple heuristic: variety in execution times suggests deliberate timing
        var hours = trades.Select(t => t.ExecutedAt.Hour).Distinct().Count();
        var timeSpread = Math.Min(hours / 12.0m, 1.0m) * 50;

        // Consistency: regular intervals suggest discipline
        var intervals = trades.OrderBy(t => t.ExecutedAt)
            .Zip(trades.OrderBy(t => t.ExecutedAt).Skip(1), (a, b) => (b.ExecutedAt - a.ExecutedAt).TotalHours)
            .ToList();

        decimal consistencyScore = 50;
        if (intervals.Count > 0)
        {
            var avgInterval = intervals.Average();
            var stdDev = Math.Sqrt(intervals.Average(i => Math.Pow(i - avgInterval, 2)));
            consistencyScore = (decimal)Math.Max(0, 50 - stdDev);
        }

        return Math.Min(Math.Round(timeSpread + consistencyScore, 2), 100);
    }

}
