using Microsoft.EntityFrameworkCore;
using EntKube.Web.Data;

namespace EntKube.Web.Services;

public class AlertSyncService(
    IServiceScopeFactory scopeFactory,
    ILogger<AlertSyncService> logger) : BackgroundService
{
    private static readonly TimeSpan Interval = TimeSpan.FromMinutes(2);
    private static readonly TimeSpan StartupDelay = TimeSpan.FromSeconds(30);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Delay(StartupDelay, stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            await SyncAllClustersAsync(stoppingToken);
            await Task.Delay(Interval, stoppingToken);
        }
    }

    private async Task SyncAllClustersAsync(CancellationToken ct)
    {
        List<Guid> clusterIds;
        using (IServiceScope scope = scopeFactory.CreateScope())
        {
            var dbFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>();
            using ApplicationDbContext db = dbFactory.CreateDbContext();

            // Only sync clusters that have a kube-prometheus-stack component installed
            clusterIds = await db.ClusterComponents
                .Where(c => c.HelmChartName == "kube-prometheus-stack"
                         && c.Status == ComponentStatus.Installed)
                .Select(c => c.ClusterId)
                .Distinct()
                .ToListAsync(ct);
        }

        foreach (Guid clusterId in clusterIds)
        {
            try
            {
                await SyncClusterAsync(clusterId, ct);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Alert sync failed for cluster {ClusterId}", clusterId);
            }
        }
    }

    private async Task SyncClusterAsync(Guid clusterId, CancellationToken ct)
    {
        using IServiceScope scope = scopeFactory.CreateScope();
        var dbFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>();
        var prometheusService = scope.ServiceProvider.GetRequiredService<PrometheusService>();
        var notificationService = scope.ServiceProvider.GetRequiredService<NotificationService>();

        // Fetch current alerts from Alertmanager
        KubernetesOperationResult<List<AlertInfo>> alertsResult =
            await prometheusService.GetAlertsAsync(clusterId, ct);

        if (!alertsResult.IsSuccess)
        {
            logger.LogDebug("Could not fetch alerts for cluster {ClusterId}: {Error}",
                clusterId, alertsResult.Error);
            return;
        }

        // Deduplicate by fingerprint in case Alertmanager returns the same alert twice
        Dictionary<string, AlertInfo> liveAlertsByFingerprint = alertsResult.Data!
            .Where(a => !string.IsNullOrEmpty(a.Fingerprint))
            .GroupBy(a => a.Fingerprint)
            .ToDictionary(g => g.Key, g => g.First());

        List<AlertInfo> liveAlerts = [.. liveAlertsByFingerprint.Values];
        HashSet<string> liveFingerprints = liveAlertsByFingerprint.Keys.ToHashSet();

        using ApplicationDbContext db = dbFactory.CreateDbContext();

        // Load ALL incidents for this cluster — including Resolved — so that re-fires
        // can reactivate an existing row rather than insert a duplicate and violate
        // the unique (ClusterId, Fingerprint) constraint.
        List<AlertIncident> allIncidents = await db.AlertIncidents
            .Where(i => i.ClusterId == clusterId)
            .ToListAsync(ct);

        Dictionary<string, AlertIncident> byFingerprint = allIncidents
            .ToDictionary(i => i.Fingerprint);

        List<AlertIncident> openIncidents = allIncidents
            .Where(i => i.Status == IncidentStatus.Active || i.Status == IncidentStatus.Acknowledged)
            .ToList();

        HashSet<string> openFingerprints = openIncidents
            .Select(i => i.Fingerprint)
            .ToHashSet();

        DateTime now = DateTime.UtcNow;
        List<AlertIncident> newIncidents = [];

        // Create or reactivate incidents for alerts not already tracked as open
        foreach (AlertInfo alert in liveAlerts)
        {
            if (string.IsNullOrEmpty(alert.Fingerprint)) continue;
            if (openFingerprints.Contains(alert.Fingerprint)) continue;

            if (byFingerprint.TryGetValue(alert.Fingerprint, out AlertIncident? existing))
            {
                // Re-fire of a previously resolved alert — reactivate in place
                existing.Status = IncidentStatus.Active;
                existing.StartsAt = alert.StartsAt == default ? now : alert.StartsAt;
                existing.EndsAt = null;
                existing.ResolvedAt = null;
                existing.AcknowledgedBy = null;
                existing.AcknowledgedAt = null;
                existing.AlertName = alert.Name;
                existing.Severity = alert.Severity;
                existing.Summary = alert.Summary;
                existing.Description = alert.Description;
                existing.LabelsJson = System.Text.Json.JsonSerializer.Serialize(alert.Labels);
                existing.UpdatedAt = now;
                newIncidents.Add(existing);
            }
            else
            {
                AlertIncident incident = new()
                {
                    ClusterId = clusterId,
                    Fingerprint = alert.Fingerprint,
                    AlertName = alert.Name,
                    Severity = alert.Severity,
                    Summary = alert.Summary,
                    Description = alert.Description,
                    LabelsJson = System.Text.Json.JsonSerializer.Serialize(alert.Labels),
                    StartsAt = alert.StartsAt == default ? now : alert.StartsAt,
                    Status = IncidentStatus.Active,
                    CreatedAt = now,
                    UpdatedAt = now
                };
                db.AlertIncidents.Add(incident);
                newIncidents.Add(incident);
            }
        }

        // Resolve incidents no longer in Alertmanager
        List<AlertIncident> resolvedIncidents = [];
        foreach (AlertIncident incident in openIncidents)
        {
            if (liveFingerprints.Contains(incident.Fingerprint))
            {
                // Still firing — update timestamp
                incident.UpdatedAt = now;
            }
            else
            {
                // No longer in Alertmanager — resolve
                incident.Status = IncidentStatus.Resolved;
                incident.EndsAt ??= now;
                incident.ResolvedAt = now;
                incident.UpdatedAt = now;
                resolvedIncidents.Add(incident);
            }
        }

        await db.SaveChangesAsync(ct);

        // Load cluster for notification dispatch and incident navigation property
        KubernetesCluster? cluster = await db.KubernetesClusters.FindAsync([clusterId], ct);
        if (cluster is null) return;

        // Dispatch notifications (need the saved incidents to have IDs)
        List<NotificationChannel> channels = await db.NotificationChannels
            .Where(c => c.TenantId == cluster.TenantId && c.IsEnabled)
            .ToListAsync(ct);

        if (channels.Count == 0) return;

        // Skip notifications while a maintenance window is active for this cluster/tenant
        bool inMaintenance = await db.MaintenanceWindows.AnyAsync(w =>
            w.TenantId == cluster.TenantId
            && w.StartsAt <= now
            && w.EndsAt >= now
            && (w.ClusterId == null || w.ClusterId == clusterId), ct);

        if (inMaintenance)
        {
            logger.LogInformation(
                "Cluster {ClusterId} is in a maintenance window — skipping notifications for {New} new / {Resolved} resolved alerts",
                clusterId, newIncidents.Count, resolvedIncidents.Count);
            return;
        }

        foreach (AlertIncident incident in newIncidents)
        {
            incident.Cluster = cluster!;
            foreach (NotificationChannel channel in channels)
            {
                await notificationService.SendAsync(incident, channel, isFiring: true, ct);
            }
        }

        foreach (AlertIncident incident in resolvedIncidents)
        {
            incident.Cluster = cluster!;
            foreach (NotificationChannel channel in channels)
            {
                await notificationService.SendAsync(incident, channel, isFiring: false, ct);
            }
        }
    }
}
