using Predictalytics.Domain.Entities;

namespace Predictalytics.Application.Services;

/// <summary>
/// Hourly bucketing for the <c>Aggregated</c> ingest tier.
/// </summary>
public static class TradeAggregation
{
    /// <summary>
    /// Aggregates trades into one VWAP row per (MarketOutcomeId, Side, hour) — but ONLY for hours
    /// that are already complete (strictly before the current UTC hour).
    ///
    /// The still-growing current hour is deliberately skipped: aggregate rows are keyed by
    /// <c>AGG_{trader}_{outcome}_{side}_{yyyyMMddHH}</c> and upserted via ON DUPLICATE KEY UPDATE,
    /// i.e. they are MUTABLE. The PnL engine, however, checkpoints positions by row Id
    /// (<c>LastAppliedTradeId</c>) — a bucket that keeps growing after it was already applied would
    /// have the same Id and its later growth would be silently skipped (Id ≤ checkpoint). Deferring
    /// the current hour until it is complete makes every PERSISTED aggregate effectively immutable,
    /// which is exactly what the checkpoint engine requires.
    ///
    /// The deferred current-hour trades are simply not returned this cycle; because raw aggregate
    /// source trades are never stored, they are re-fetched next cycle and aggregated once their hour
    /// has closed.
    /// </summary>
    public static List<Trade> AggregateCompletedHours(IEnumerable<Trade> trades, int traderId, DateTime nowUtc)
    {
        var currentHourKey = nowUtc.ToString("yyyyMMddHH");
        var result = new List<Trade>();

        foreach (var grp in trades
                     .Where(t => t.MarketOutcomeId.HasValue)
                     .GroupBy(t => new { t.MarketOutcomeId, t.Side, Hour = t.ExecutedAt.ToString("yyyyMMddHH") }))
        {
            if (grp.Key.Hour == currentHourKey)
                continue; // defer the still-growing current hour

            var first = grp.First();
            var totalAmount = grp.Sum(t => t.Amount);
            var totalSize = grp.Sum(t => t.Size);
            var vwap = totalSize > 0 ? totalAmount / totalSize : first.Price;

            result.Add(new Trade
            {
                PlatformTradeId = $"AGG_{traderId}_{grp.Key.MarketOutcomeId}_{grp.Key.Side}_{grp.Key.Hour}",
                TraderId = traderId,
                MarketOutcomeId = first.MarketOutcomeId,
                DbMarketId = first.DbMarketId,
                MarketId = first.MarketId,
                AssetId = first.AssetId,
                Outcome = first.Outcome,
                Side = first.Side,
                Price = vwap,
                Amount = totalAmount,
                Size = totalSize,
                ExecutedAt = first.ExecutedAt,
                AggregatedCount = grp.Count()
            });
        }

        return result;
    }
}
