

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

public sealed class AlertQualityAggregatorService : BackgroundService
{
    private readonly IServiceScopeFactory _scopes;
    private readonly IOptions<AlertQualityOptions> _options;
    private readonly ClinicalMetrics _metrics;
    private readonly ILogger<AlertQualityAggregatorService> _logger;

    public AlertQualityAggregatorService(
        IServiceScopeFactory scopes,
        IOptions<AlertQualityOptions> options,
        ClinicalMetrics metrics,
        ILogger<AlertQualityAggregatorService> logger)
    {
        _scopes = scopes;
        _options = options;
        _metrics = metrics;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var interval = TimeSpan.FromMinutes(Math.Max(1, _options.Value.IntervalMinutes));
        using var timer = new PeriodicTimer(interval);

        // Run once at startup, then on interval
        await AggregateAsync(stoppingToken);

        while (await timer.WaitForNextTickAsync(stoppingToken))
            await AggregateAsync(stoppingToken);
    }

    private async Task AggregateAsync(CancellationToken ct)
    {
        try
        {
            await using var scope = _scopes.CreateAsyncScope();
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var windowHours = Math.Max(1, _options.Value.WindowHours);
            var windowEnd = AlignToHour(DateTimeOffset.UtcNow);
            var windowStart = windowEnd.AddHours(-windowHours);

            foreach (AlertType alertType in Enum.GetValues<AlertType>())
            {
                var alerts = await db.ClinicalAlerts
                    .AsNoTracking()
                    .Where(a => a.AlertType == alertType
                             && a.TriggeredAt >= windowStart
                             && a.TriggeredAt < windowEnd)
                    .Select(a => new
                    {
                        a.Status,
                        a.TriggeredAt,
                        a.AcknowledgedAt,
                        a.ResolvedAt
                    })
                    .ToListAsync(ct);

                if (alerts.Count == 0)
                    continue;

                var alertIds = await db.ClinicalAlerts
                    .AsNoTracking()
                    .Where(a => a.AlertType == alertType
                             && a.TriggeredAt >= windowStart
                             && a.TriggeredAt < windowEnd)
                    .Select(a => a.Id)
                    .ToListAsync(ct);

                var feedbacks = await db.AlertFeedbacks
                    .AsNoTracking()
                    .Where(f => alertIds.Contains(f.AlertId))
                    .Select(f => f.FeedbackType)
                    .ToListAsync(ct);

                var total = alerts.Count;
                var acknowledged = alerts.Count(a =>
                    a.Status is AlertStatus.Acknowledged or AlertStatus.Resolved);
                var resolved = alerts.Count(a => a.Status == AlertStatus.Resolved);
                var escalated = alerts.Count(a => a.Status == AlertStatus.Escalated);

                var useful = feedbacks.Count(f => f == AlertFeedbackType.Useful);
                var falsePositive = feedbacks.Count(f => f == AlertFeedbackType.FalsePositive);
                var wouldAct = feedbacks.Count(f => f == AlertFeedbackType.WouldAct);
                var feedbackCount = feedbacks.Count;

                var ackDurations = alerts
                    .Where(a => a.AcknowledgedAt.HasValue)
                    .Select(a => (a.AcknowledgedAt!.Value - a.TriggeredAt).TotalSeconds)
                    .ToList();

                var resolveDurations = alerts
                    .Where(a => a.ResolvedAt.HasValue && a.AcknowledgedAt.HasValue)
                    .Select(a => (a.ResolvedAt!.Value - a.AcknowledgedAt!.Value).TotalSeconds)
                    .ToList();

                var metric = new AlertQualityMetric
                {
                    Id = Guid.NewGuid(),
                    AlertType = alertType,
                    WindowStart = windowStart,
                    WindowEnd = windowEnd,
                    TotalAlerts = total,
                    AcknowledgedCount = acknowledged,
                    ResolvedCount = resolved,
                    EscalatedCount = escalated,
                    FeedbackUsefulCount = useful,
                    FeedbackFalsePositiveCount = falsePositive,
                    FeedbackWouldActCount = wouldAct,
                    FeedbackCount = feedbackCount,
                    AcknowledgementRate = SafeRate(acknowledged, total),
                    FalsePositiveRate = SafeRate(falsePositive, feedbackCount),
                    UsefulRate = SafeRate(useful, feedbackCount),
                    WouldActRate = SafeRate(wouldAct, feedbackCount),
                    AvgSecondsToAcknowledge = ackDurations.Count > 0 ? ackDurations.Average() : 0,
                    AvgSecondsToResolution = resolveDurations.Count > 0 ? resolveDurations.Average() : 0,
                    ComputedAt = DateTimeOffset.UtcNow
                };

                var existing = await db.AlertQualityMetrics
                    .FirstOrDefaultAsync(m =>
                        m.AlertType == alertType
                        && m.WindowStart == windowStart
                        && m.WindowEnd == windowEnd, ct);

                if (existing is null)
                    db.AlertQualityMetrics.Add(metric);
                else
                {
                    existing.TotalAlerts = metric.TotalAlerts;
                    existing.AcknowledgedCount = metric.AcknowledgedCount;
                    existing.ResolvedCount = metric.ResolvedCount;
                    existing.EscalatedCount = metric.EscalatedCount;
                    existing.FeedbackUsefulCount = metric.FeedbackUsefulCount;
                    existing.FeedbackFalsePositiveCount = metric.FeedbackFalsePositiveCount;
                    existing.FeedbackWouldActCount = metric.FeedbackWouldActCount;
                    existing.FeedbackCount = metric.FeedbackCount;
                    existing.AcknowledgementRate = metric.AcknowledgementRate;
                    existing.FalsePositiveRate = metric.FalsePositiveRate;
                    existing.UsefulRate = metric.UsefulRate;
                    existing.WouldActRate = metric.WouldActRate;
                    existing.AvgSecondsToAcknowledge = metric.AvgSecondsToAcknowledge;
                    existing.AvgSecondsToResolution = metric.AvgSecondsToResolution;
                    existing.ComputedAt = metric.ComputedAt;
                    metric = existing;
                }

                await db.SaveChangesAsync(ct);

                var typeLabel = alertType.ToDbString();
                _metrics.AlertAcknowledgementRate.WithLabels(typeLabel).Set(metric.AcknowledgementRate);
                _metrics.AlertFalsePositiveRate.WithLabels(typeLabel).Set(metric.FalsePositiveRate);
                _metrics.AlertUsefulRate.WithLabels(typeLabel).Set(metric.UsefulRate);
                _metrics.AlertAvgAckSeconds.WithLabels(typeLabel).Set(metric.AvgSecondsToAcknowledge);
            }

            _logger.LogDebug(
                "Alert quality aggregation complete for window {Start} – {End}",
                windowStart, windowEnd);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "AlertQualityAggregatorService failed");
        }
    }

    private static DateTimeOffset AlignToHour(DateTimeOffset value) =>
        new(value.Year, value.Month, value.Day, value.Hour, 0, 0, value.Offset);

    private static double SafeRate(int numerator, int denominator) =>
        denominator == 0 ? 0.0 : (double)numerator / denominator;
}