using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using EntKube.Web.Data;

namespace EntKube.Web.Services;

public class IncidentService(IDbContextFactory<ApplicationDbContext> dbFactory)
{
    /// <summary>
    /// Returns incidents that are relevant to a specific set of deployments,
    /// matched by cluster ID and namespace label in the alert's LabelsJson.
    /// Used by the customer portal to show incidents scoped to the customer's apps.
    /// </summary>
    public async Task<List<AlertIncident>> GetIncidentsForDeploymentsAsync(
        List<AppDeployment> deployments,
        int windowDays = 30,
        IncidentStatus? status = null,
        CancellationToken ct = default)
    {
        if (deployments.Count == 0) return [];

        using ApplicationDbContext db = dbFactory.CreateDbContext();
        DateTime from = DateTime.UtcNow.AddDays(-windowDays);

        HashSet<Guid> clusterIds = deployments.Select(d => d.ClusterId).ToHashSet();
        HashSet<string> namespaces = deployments.Select(d => d.Namespace).ToHashSet();

        IQueryable<AlertIncident> query = db.AlertIncidents
            .Include(i => i.Cluster)
            .Include(i => i.Notes.OrderBy(n => n.CreatedAt))
            .Where(i => clusterIds.Contains(i.ClusterId) && i.StartsAt >= from)
            .OrderByDescending(i => i.StartsAt);

        if (status.HasValue)
            query = query.Where(i => i.Status == status.Value);

        List<AlertIncident> all = await query.ToListAsync(ct);

        // Filter client-side by namespace label (JSON parsing can't run in EF)
        return all.Where(i =>
        {
            try
            {
                using JsonDocument doc = JsonDocument.Parse(i.LabelsJson);
                return !doc.RootElement.TryGetProperty("namespace", out JsonElement ns)
                       || namespaces.Contains(ns.GetString() ?? "");
            }
            catch { return true; }
        }).ToList();
    }

    public async Task<List<MaintenanceWindow>> GetUpcomingMaintenanceAsync(
        Guid tenantId, HashSet<Guid>? clusterIds = null, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        DateTime now = DateTime.UtcNow;
        DateTime lookahead = now.AddDays(7);

        List<MaintenanceWindow> windows = await db.MaintenanceWindows
            .Include(w => w.Cluster)
            .Where(w => w.TenantId == tenantId
                     && w.EndsAt >= now
                     && w.StartsAt <= lookahead)
            .OrderBy(w => w.StartsAt)
            .ToListAsync(ct);

        if (clusterIds is not null && clusterIds.Count > 0)
            windows = windows.Where(w => w.ClusterId == null || clusterIds.Contains(w.ClusterId.Value)).ToList();

        return windows;
    }


    public async Task<List<AlertIncident>> GetIncidentsForTenantAsync(
        Guid tenantId,
        IncidentStatus? status = null,
        string? severity = null,
        Guid? clusterId = null,
        DateTime? from = null,
        DateTime? to = null,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();

        IQueryable<AlertIncident> query = db.AlertIncidents
            .Include(i => i.Cluster)
            .Include(i => i.Notes.OrderBy(n => n.CreatedAt))
            .Where(i => i.Cluster.TenantId == tenantId)
            .OrderByDescending(i => i.StartsAt);

        if (status.HasValue)
            query = query.Where(i => i.Status == status.Value);
        if (!string.IsNullOrEmpty(severity))
            query = query.Where(i => i.Severity == severity);
        if (clusterId.HasValue)
            query = query.Where(i => i.ClusterId == clusterId.Value);
        if (from.HasValue)
            query = query.Where(i => i.StartsAt >= from.Value);
        if (to.HasValue)
            query = query.Where(i => i.StartsAt <= to.Value);

        return await query.ToListAsync(ct);
    }

    public async Task<AlertIncident?> GetByIdAsync(Guid id, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.AlertIncidents
            .Include(i => i.Cluster)
            .Include(i => i.Notes.OrderBy(n => n.CreatedAt))
            .FirstOrDefaultAsync(i => i.Id == id, ct);
    }

    public async Task AcknowledgeAsync(Guid id, string acknowledgedBy, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AlertIncident? incident = await db.AlertIncidents.FindAsync([id], ct);
        if (incident is null || incident.Status != IncidentStatus.Active) return;

        incident.Status = IncidentStatus.Acknowledged;
        incident.AcknowledgedBy = acknowledgedBy;
        incident.AcknowledgedAt = DateTime.UtcNow;
        incident.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(ct);
    }

    public async Task ResolveAsync(Guid id, string resolvedBy, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AlertIncident? incident = await db.AlertIncidents.FindAsync([id], ct);
        if (incident is null || incident.Status == IncidentStatus.Resolved) return;

        incident.Status = IncidentStatus.Resolved;
        incident.ResolvedAt = DateTime.UtcNow;
        incident.EndsAt ??= DateTime.UtcNow;
        incident.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(ct);
    }

    public async Task AddNoteAsync(Guid incidentId, string author, string content, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        db.IncidentNotes.Add(new IncidentNote
        {
            IncidentId = incidentId,
            Author = author,
            Content = content
        });
        await db.SaveChangesAsync(ct);
    }

    public async Task<UptimeResult> GetUptimeAsync(
        Guid deploymentId,
        int windowDays,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        DateTime from = DateTime.UtcNow.AddDays(-windowDays);

        List<DeploymentHealthSnapshot> snapshots = await db.DeploymentHealthSnapshots
            .Where(s => s.DeploymentId == deploymentId && s.SnapshotAt >= from)
            .OrderBy(s => s.SnapshotAt)
            .ToListAsync(ct);

        if (snapshots.Count == 0)
            return new UptimeResult(windowDays, null);

        int healthy = snapshots.Count(s => s.HealthStatus == HealthStatus.Healthy);
        double percent = (double)healthy / snapshots.Count * 100.0;
        return new UptimeResult(windowDays, Math.Round(percent, 2));
    }

    public async Task<int> GetActiveAlertCountForTenantAsync(Guid tenantId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.AlertIncidents
            .CountAsync(i => i.Cluster.TenantId == tenantId
                && (i.Status == IncidentStatus.Active || i.Status == IncidentStatus.Acknowledged), ct);
    }

    public async Task<AlertStats> GetStatsForTenantAsync(
        Guid tenantId, int windowDays = 30, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        DateTime from = DateTime.UtcNow.AddDays(-windowDays);

        List<AlertIncident> incidents = await db.AlertIncidents
            .Where(i => i.Cluster.TenantId == tenantId && i.StartsAt >= from)
            .ToListAsync(ct);

        int total = incidents.Count;
        int active = incidents.Count(i => i.Status == IncidentStatus.Active);
        int acked = incidents.Count(i => i.Status == IncidentStatus.Acknowledged);
        int resolved = incidents.Count(i => i.Status == IncidentStatus.Resolved);
        int critical = incidents.Count(i => i.Severity == "critical");
        int warning = incidents.Count(i => i.Severity == "warning");

        List<AlertIncident> resolvedWithTime = incidents
            .Where(i => i.Status == IncidentStatus.Resolved && i.ResolvedAt.HasValue)
            .ToList();

        double? mttrMinutes = resolvedWithTime.Count > 0
            ? resolvedWithTime.Average(i => (i.ResolvedAt!.Value - i.StartsAt).TotalMinutes)
            : null;

        List<AlertIncident> ackedWithTime = incidents
            .Where(i => i.AcknowledgedAt.HasValue)
            .ToList();

        double? mttaMinutes = ackedWithTime.Count > 0
            ? ackedWithTime.Average(i => (i.AcknowledgedAt!.Value - i.StartsAt).TotalMinutes)
            : null;

        // Daily buckets for sparkline
        List<DailyAlertCount> daily = incidents
            .GroupBy(i => i.StartsAt.Date)
            .Select(g => new DailyAlertCount(g.Key, g.Count()))
            .OrderBy(d => d.Date)
            .ToList();

        return new AlertStats(
            WindowDays: windowDays,
            Total: total,
            Active: active,
            Acknowledged: acked,
            Resolved: resolved,
            Critical: critical,
            Warning: warning,
            MttrMinutes: mttrMinutes,
            MttaMinutes: mttaMinutes,
            Daily: daily);
    }

    public async Task<bool> IsInMaintenanceAsync(Guid tenantId, Guid? clusterId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        DateTime now = DateTime.UtcNow;
        return await db.MaintenanceWindows.AnyAsync(w =>
            w.TenantId == tenantId
            && w.StartsAt <= now
            && w.EndsAt >= now
            && (w.ClusterId == null || w.ClusterId == clusterId), ct);
    }

    public async Task<List<MaintenanceWindow>> GetMaintenanceWindowsAsync(
        Guid tenantId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.MaintenanceWindows
            .Include(w => w.Cluster)
            .Where(w => w.TenantId == tenantId)
            .OrderByDescending(w => w.StartsAt)
            .ToListAsync(ct);
    }

    public async Task CreateMaintenanceWindowAsync(
        Guid tenantId, string title, string? description, Guid? clusterId,
        DateTime startsAt, DateTime endsAt, string createdBy,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        db.MaintenanceWindows.Add(new MaintenanceWindow
        {
            TenantId = tenantId,
            ClusterId = clusterId,
            Title = title,
            Description = description,
            StartsAt = startsAt,
            EndsAt = endsAt,
            CreatedBy = createdBy
        });
        await db.SaveChangesAsync(ct);
    }

    public async Task DeleteMaintenanceWindowAsync(Guid id, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        MaintenanceWindow? w = await db.MaintenanceWindows.FindAsync([id], ct);
        if (w is not null) db.MaintenanceWindows.Remove(w);
        await db.SaveChangesAsync(ct);
    }

    public async Task<SlaTarget?> GetSlaTargetAsync(
        Guid tenantId, Guid? customerId, Guid? appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        // Most specific target wins: app > customer > tenant
        return await db.SlaTargets
            .Where(s => s.TenantId == tenantId
                && (appId == null || s.AppId == appId || s.AppId == null)
                && (customerId == null || s.CustomerId == customerId || s.CustomerId == null))
            .OrderByDescending(s => s.AppId.HasValue)
            .ThenByDescending(s => s.CustomerId.HasValue)
            .FirstOrDefaultAsync(ct);
    }

    public async Task UpsertSlaTargetAsync(
        Guid tenantId, Guid? customerId, Guid? appId,
        double targetPercent, int windowDays,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        SlaTarget? existing = await db.SlaTargets
            .FirstOrDefaultAsync(s => s.TenantId == tenantId
                && s.CustomerId == customerId && s.AppId == appId, ct);

        if (existing is null)
        {
            db.SlaTargets.Add(new SlaTarget
            {
                TenantId = tenantId,
                CustomerId = customerId,
                AppId = appId,
                TargetPercent = targetPercent,
                MeasurementWindowDays = windowDays
            });
        }
        else
        {
            existing.TargetPercent = targetPercent;
            existing.MeasurementWindowDays = windowDays;
        }
        await db.SaveChangesAsync(ct);
    }
}

public record UptimeResult(int WindowDays, double? Percent)
{
    public string Display => Percent.HasValue ? $"{Percent:F2}%" : "No data";
}

public record AlertStats(
    int WindowDays,
    int Total,
    int Active,
    int Acknowledged,
    int Resolved,
    int Critical,
    int Warning,
    double? MttrMinutes,
    double? MttaMinutes,
    List<DailyAlertCount> Daily)
{
    public string FormatMttr() => MttrMinutes.HasValue
        ? MttrMinutes.Value >= 60
            ? $"{MttrMinutes.Value / 60:F1}h"
            : $"{(int)MttrMinutes.Value}m"
        : "—";

    public string FormatMtta() => MttaMinutes.HasValue
        ? MttaMinutes.Value >= 60
            ? $"{MttaMinutes.Value / 60:F1}h"
            : $"{(int)MttaMinutes.Value}m"
        : "—";
}

public record DailyAlertCount(DateTime Date, int Count);
