using Dapper;
using Microsoft.Extensions.Logging;
using Quartz;
using SIGCM2.Infrastructure.Persistence;

namespace SIGCM2.Infrastructure.Audit.Jobs;

/// <summary>
/// UDT-010 (#REQ-AUD-6) — monthly maintenance:
/// - Extends the forward boundary of pf_AuditEvent_Monthly and pf_SecurityEvent_Monthly
///   so the next month always has a partition ready (SPLIT RANGE).
/// - Intended schedule: cron '0 0 2 1 * ?' (day 1 each month at 02:00 UTC).
/// - Idempotent: only splits if the target boundary does not yet exist.
/// </summary>
[DisallowConcurrentExecution]
public sealed class AuditPartitionManagerJob : IJob
{
    public const string CronSchedule = "0 0 2 1 * ?";

    private readonly SqlConnectionFactory _factory;
    private readonly ILogger<AuditPartitionManagerJob> _logger;
    private readonly TimeProvider _timeProvider;

    public AuditPartitionManagerJob(SqlConnectionFactory factory, ILogger<AuditPartitionManagerJob> logger, TimeProvider timeProvider)
    {
        _factory = factory;
        _logger = logger;
        _timeProvider = timeProvider;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        var ct = context.CancellationToken;
        await using var conn = _factory.CreateConnection();
        await conn.OpenAsync(ct);

        // Target: boundary for "next month + 1" (so the next month is always pre-created and we
        // keep at least one boundary ahead after the rotation).
        var now = _timeProvider.GetUtcNow().UtcDateTime;
        var target = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(2);

        var affected = 0;
        foreach (var pf in new[] { "pf_AuditEvent_Monthly", "pf_SecurityEvent_Monthly" })
        {
            var exists = await conn.ExecuteScalarAsync<int>("""
                SELECT COUNT(*)
                FROM sys.partition_functions pf
                JOIN sys.partition_range_values prv ON prv.function_id = pf.function_id
                WHERE pf.name = @Name AND CAST(prv.value AS DATETIME2(3)) = @Target;
                """, new { Name = pf, Target = target });

            if (exists == 0)
            {
                // Parameterized partition function name would require dynamic SQL; whitelisted above.
                var sql = $"ALTER PARTITION FUNCTION {pf}() SPLIT RANGE (@Target);";
                await conn.ExecuteAsync(sql, new { Target = target });
                affected++;
                _logger.LogInformation("Partition boundary {Boundary:yyyy-MM-dd} added to {Function}", target, pf);
            }
        }

        _logger.LogInformation(
            "AuditPartitionManagerJob completed — {Affected} partition function(s) extended (target: {Target:yyyy-MM-dd})",
            affected, target);
    }
}
