using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Quartz;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;
using OTSSignsOrchestrator.Server.Hubs;

namespace OTSSignsOrchestrator.Server.Jobs;

/// <summary>
/// Quartz job that runs daily to check BYOI certificate expiry dates across all enabled
/// ByoiConfig entries. Alerts at 60, 30, and 7 day thresholds via FleetHub and logs to AuditLog.
///
/// Severity escalation:
///   - &gt; 7 days remaining  → "Warning"
///   - ≤ 7 days remaining  → "Critical"
/// </summary>
// IMMUTABLE AuditLog — this job only appends, never updates or deletes audit records.
[DisallowConcurrentExecution]
public sealed class ByoiCertExpiryJob : IJob
{
    /// <summary>Alert thresholds in days. Alerts fire when remaining days ≤ threshold.</summary>
    internal static readonly int[] AlertThresholdDays = [60, 30, 7];

    /// <summary>Days at or below which severity escalates to "Critical".</summary>
    internal const int CriticalThresholdDays = 7;

    private readonly IServiceProvider _services;
    private readonly ILogger<ByoiCertExpiryJob> _logger;

    public ByoiCertExpiryJob(
        IServiceProvider services,
        ILogger<ByoiCertExpiryJob> logger)
    {
        _services = services;
        _logger = logger;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        await using var scope = _services.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();
        var hub = scope.ServiceProvider.GetRequiredService<IHubContext<FleetHub, IFleetClient>>();

        var configs = await db.ByoiConfigs
            .AsNoTracking()
            .Include(b => b.Instance)
                .ThenInclude(i => i.Customer)
            .Where(b => b.Enabled)
            .ToListAsync(context.CancellationToken);

        foreach (var config in configs)
        {
            var daysRemaining = (config.CertExpiry - DateTime.UtcNow).TotalDays;
            var abbrev = config.Instance.Customer.Abbreviation;

            if (!ShouldAlert(daysRemaining))
                continue;

            var severity = GetSeverity(daysRemaining);
            var daysInt = (int)Math.Floor(daysRemaining);
            var message = daysRemaining <= 0
                ? $"BYOI cert for {abbrev} has EXPIRED."
                : $"BYOI cert for {abbrev} expires in {daysInt} days.";

            _logger.LogWarning("BYOI cert expiry alert: {Severity} — {Message}", severity, message);

            await hub.Clients.All.SendAlertRaised(severity, message);

            // Append-only audit log
            db.AuditLogs.Add(new AuditLog
            {
                Id = Guid.NewGuid(),
                InstanceId = config.InstanceId,
                Actor = "ByoiCertExpiryJob",
                Action = "CertExpiryAlert",
                Target = config.Slug,
                Outcome = severity,
                Detail = message,
                OccurredAt = DateTime.UtcNow,
            });
        }

        await db.SaveChangesAsync(context.CancellationToken);
    }

    /// <summary>
    /// Determines whether an alert should fire based on remaining days.
    /// Alerts at ≤ 60, ≤ 30, ≤ 7 days (or already expired).
    /// </summary>
    internal static bool ShouldAlert(double daysRemaining)
    {
        foreach (var threshold in AlertThresholdDays)
        {
            if (daysRemaining <= threshold)
                return true;
        }
        return false;
    }

    /// <summary>
    /// Returns "Critical" when ≤ 7 days remain, otherwise "Warning".
    /// </summary>
    internal static string GetSeverity(double daysRemaining) =>
        daysRemaining <= CriticalThresholdDays ? "Critical" : "Warning";
}
