using System.Text.Json; using LiveRecorder.Application.Abstractions.Persistence; using LiveRecorder.Application.Models.RecordTasks; using LiveRecorder.Domain.Enums; namespace LiveRecorder.Application.Services; public sealed class BandwidthStatisticsService { private const string BandwidthCategory = "Bandwidth"; private const string SampleMessage = "bandwidth_sample"; private readonly ISystemLogRepository _systemLogRepository; public BandwidthStatisticsService(ISystemLogRepository systemLogRepository) { _systemLogRepository = systemLogRepository; } public async Task GetSessionTimelineAsync(Guid recordSessionId, CancellationToken cancellationToken = default) { // Query all bandwidth sample log entries for this session // Since there's no dedicated method, we use ListAsync with category filter var allLogs = await _systemLogRepository.ListAllAsync(cancellationToken); var bandwidthLogs = allLogs .Where(item => item.Category == BandwidthCategory && item.Message == SampleMessage && item.RecordSessionId == recordSessionId) .OrderBy(item => item.CreatedAt) .ToList(); if (bandwidthLogs.Count == 0) { return null; } var points = new List(bandwidthLogs.Count); foreach (var entry in bandwidthLogs) { if (string.IsNullOrWhiteSpace(entry.Detail)) { continue; } try { using var doc = JsonDocument.Parse(entry.Detail); long bytesDownloaded = 0; double? bitrateKbps = null; if (doc.RootElement.TryGetProperty("bytesDownloaded", out var bd) && bd.TryGetInt64(out var bv)) bytesDownloaded = bv; if (doc.RootElement.TryGetProperty("bitrateKbps", out var br) && br.TryGetDouble(out var bkv)) bitrateKbps = bkv; points.Add(new BandwidthPointDto { Timestamp = entry.CreatedAt, BytesDownloaded = bytesDownloaded, BitrateKbps = bitrateKbps }); } catch { // Skip malformed entries } } return new BandwidthTimelineDto { RecordSessionId = recordSessionId, Points = points }; } public async Task GetDailySummaryAsync(DateOnly date, int utcOffsetMinutes, CancellationToken cancellationToken = default) { var windowStartUtc = new DateTimeOffset(date.ToDateTime(TimeOnly.MinValue), TimeSpan.FromMinutes(utcOffsetMinutes)); var windowEndUtc = new DateTimeOffset(date.ToDateTime(TimeOnly.MaxValue), TimeSpan.FromMinutes(utcOffsetMinutes)); var allLogs = await _systemLogRepository.ListAllAsync(cancellationToken); var bandwidthLogs = allLogs .Where(item => item.Category == BandwidthCategory && item.Message == SampleMessage && item.CreatedAt >= windowStartUtc && item.CreatedAt <= windowEndUtc) .OrderBy(item => item.CreatedAt) .ToList(); if (bandwidthLogs.Count == 0) { return null; } var bitrates = new List(); long maxBytes = 0; long finalBytes = 0; foreach (var entry in bandwidthLogs) { if (string.IsNullOrWhiteSpace(entry.Detail)) continue; try { using var doc = JsonDocument.Parse(entry.Detail); if (doc.RootElement.TryGetProperty("bytesDownloaded", out var bd) && bd.TryGetInt64(out var bv)) { if (bv > maxBytes) maxBytes = bv; finalBytes = bv; } if (doc.RootElement.TryGetProperty("bitrateKbps", out var br) && br.TryGetDouble(out var bkv)) { bitrates.Add(bkv); } } catch { // Skip } } var avgBitrate = bitrates.Count > 0 ? bitrates.Average() : 0; var peakBitrate = bitrates.Count > 0 ? bitrates.Max() : 0; return new BandwidthSummaryDto { TotalTrafficMB = finalBytes / (1024.0 * 1024.0), AverageBitrateKbps = avgBitrate, PeakBitrateKbps = peakBitrate }; } }