using Microsoft.EntityFrameworkCore; using SPMS.Domain.Entities; using SPMS.Domain.Enums; using SPMS.Domain.Interfaces; namespace SPMS.Infrastructure.Persistence.Repositories; public class PushSendLogRepository : Repository, IPushSendLogRepository { public PushSendLogRepository(AppDbContext context) : base(context) { } public async Task<(IReadOnlyList Items, int TotalCount)> GetPagedWithMessageAsync( long serviceId, int page, int size, long? messageId = null, long? deviceId = null, PushResult? status = null, DateTime? startDate = null, DateTime? endDate = null) { var query = _dbSet .Include(l => l.Message) .Where(l => l.ServiceId == serviceId); if (messageId.HasValue) query = query.Where(l => l.MessageId == messageId.Value); if (deviceId.HasValue) query = query.Where(l => l.DeviceId == deviceId.Value); if (status.HasValue) query = query.Where(l => l.Status == status.Value); if (startDate.HasValue) query = query.Where(l => l.SentAt >= startDate.Value); if (endDate.HasValue) query = query.Where(l => l.SentAt < endDate.Value.AddDays(1)); var totalCount = await query.CountAsync(); var items = await query .OrderByDescending(l => l.SentAt) .Skip((page - 1) * size) .Take(size) .ToListAsync(); return (items, totalCount); } public async Task> GetHourlyStatsAsync(long? serviceId, DateTime startDate, DateTime endDate) { var query = _dbSet.Where(l => l.SentAt >= startDate && l.SentAt < endDate); if (serviceId.HasValue) query = query.Where(l => l.ServiceId == serviceId.Value); return await query .GroupBy(l => l.SentAt.Hour) .Select(g => new HourlyStatRaw { Hour = g.Key, SendCount = g.Count(), SuccessCount = g.Count(l => l.Status == PushResult.Success), FailCount = g.Count(l => l.Status == PushResult.Failed) }) .OrderBy(h => h.Hour) .ToListAsync(); } public async Task GetMessageStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate) { var query = _dbSet.Where(l => l.ServiceId == serviceId && l.MessageId == messageId); if (startDate.HasValue) query = query.Where(l => l.SentAt >= startDate.Value); if (endDate.HasValue) query = query.Where(l => l.SentAt < endDate.Value.AddDays(1)); var hasData = await query.AnyAsync(); if (!hasData) return null; return await query .GroupBy(_ => 1) .Select(g => new MessageStatRaw { TotalSend = g.Count(), TotalSuccess = g.Count(l => l.Status == PushResult.Success), TotalFail = g.Count(l => l.Status == PushResult.Failed), FirstSentAt = g.Min(l => l.SentAt), LastSentAt = g.Max(l => l.SentAt) }) .FirstOrDefaultAsync(); } public async Task<(IReadOnlyList Items, int TotalCount)> GetDetailLogPagedAsync( long serviceId, long messageId, int page, int size, PushResult? status = null, Platform? platform = null) { var query = _dbSet .Include(l => l.Device) .Where(l => l.ServiceId == serviceId && l.MessageId == messageId); if (status.HasValue) query = query.Where(l => l.Status == status.Value); if (platform.HasValue) query = query.Where(l => l.Device.Platform == platform.Value); var totalCount = await query.CountAsync(); var items = await query .OrderByDescending(l => l.SentAt) .Skip((page - 1) * size) .Take(size) .ToListAsync(); return (items, totalCount); } public async Task> GetExportLogsAsync( long serviceId, DateTime startDate, DateTime endDate, long? messageId = null, long? deviceId = null, PushResult? status = null, int maxCount = 100000) { var query = _dbSet .Include(l => l.Message) .Include(l => l.Device) .Where(l => l.ServiceId == serviceId && l.SentAt >= startDate && l.SentAt < endDate); if (messageId.HasValue) query = query.Where(l => l.MessageId == messageId.Value); if (deviceId.HasValue) query = query.Where(l => l.DeviceId == deviceId.Value); if (status.HasValue) query = query.Where(l => l.Status == status.Value); return await query .OrderByDescending(l => l.SentAt) .Take(maxCount) .ToListAsync(); } public async Task> GetMessageDailyStatsAsync(long serviceId, long messageId, DateTime? startDate, DateTime? endDate) { var query = _dbSet.Where(l => l.ServiceId == serviceId && l.MessageId == messageId); if (startDate.HasValue) query = query.Where(l => l.SentAt >= startDate.Value); if (endDate.HasValue) query = query.Where(l => l.SentAt < endDate.Value.AddDays(1)); return await query .GroupBy(l => DateOnly.FromDateTime(l.SentAt)) .Select(g => new MessageDailyStatRaw { StatDate = g.Key, SendCount = g.Count(), SuccessCount = g.Count(l => l.Status == PushResult.Success) }) .OrderByDescending(d => d.StatDate) .ToListAsync(); } public async Task<(IReadOnlyList Items, int TotalCount)> GetMessageHistoryPagedAsync( long? serviceId, int page, int size, string? keyword = null, string? status = null, DateTime? startDate = null, DateTime? endDate = null) { var query = _dbSet .Include(l => l.Message) .Include(l => l.Message.Service) .Where(l => !l.Message.IsDeleted); if (serviceId.HasValue) query = query.Where(l => l.ServiceId == serviceId.Value); if (!string.IsNullOrWhiteSpace(keyword)) query = query.Where(l => l.Message.MessageCode.Contains(keyword) || l.Message.Title.Contains(keyword)); if (startDate.HasValue) query = query.Where(l => l.SentAt >= startDate.Value); if (endDate.HasValue) query = query.Where(l => l.SentAt < endDate.Value.AddDays(1)); // 메시지별 GroupBy 집계 var grouped = query .GroupBy(l => new { l.MessageId, l.Message.MessageCode, l.Message.Title, ServiceName = l.Message.Service.ServiceName }) .Select(g => new MessageHistorySummary { MessageId = g.Key.MessageId, MessageCode = g.Key.MessageCode, Title = g.Key.Title, ServiceName = g.Key.ServiceName, FirstSentAt = g.Min(l => l.SentAt), TotalSendCount = g.Count(), SuccessCount = g.Count(l => l.Status == PushResult.Success), FailCount = g.Count(l => l.Status == PushResult.Failed) }); // status 필터 (DB 레벨) if (!string.IsNullOrWhiteSpace(status)) { grouped = status.ToLowerInvariant() switch { "complete" => grouped.Where(g => g.SuccessCount > 0), "failed" => grouped.Where(g => g.TotalSendCount > 0 && g.SuccessCount == 0), "pending" => grouped.Where(g => g.TotalSendCount == 0), _ => grouped }; } var totalCount = await grouped.CountAsync(); var items = await grouped .OrderByDescending(g => g.FirstSentAt) .Skip((page - 1) * size) .Take(size) .ToListAsync(); return (items, totalCount); } public async Task> GetFailureStatsByMessageAsync(long serviceId, long messageId, int limit) { return await _dbSet .Where(l => l.ServiceId == serviceId && l.MessageId == messageId && l.Status == PushResult.Failed && l.FailReason != null) .GroupBy(l => l.FailReason!) .Select(g => new FailureStatRaw { FailReason = g.Key, Count = g.Count() }) .OrderByDescending(f => f.Count) .Take(limit) .ToListAsync(); } public async Task> GetFailureStatsAsync(long? serviceId, DateTime startDate, DateTime endDate, int limit) { var query = _dbSet.Where(l => l.Status == PushResult.Failed && l.SentAt >= startDate && l.SentAt < endDate && l.FailReason != null); if (serviceId.HasValue) query = query.Where(l => l.ServiceId == serviceId.Value); return await query .GroupBy(l => l.FailReason!) .Select(g => new FailureStatRaw { FailReason = g.Key, Count = g.Count() }) .OrderByDescending(f => f.Count) .Take(limit) .ToListAsync(); } }