using GB5Shared.DTO.Framework.Login;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace FrameworkBLL.SchedulerTaskGenerator
{
///
/// Background service that polls for ready scheduler tasks on a fixed interval.
/// Scoped services (BLL, DAL) are resolved per cycle via IServiceScopeFactory.
///
/// Use this when Quartz is NOT available (simple deployments, Docker, etc.)
/// Use SchedulerQuartzJob instead when precise cron-based triggering of the
/// poll cycle itself is required.
///
public sealed class SchedulerBackgroundService : BackgroundService
{
private readonly IServiceProvider _services;
private readonly IConfiguration _config;
private readonly ILogger _logger;
private readonly TimeSpan _pollingInterval;
public SchedulerBackgroundService(
IServiceProvider services,
IConfiguration config,
ILogger logger,
TimeSpan? pollingInterval = null)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pollingInterval = pollingInterval ?? TimeSpan.FromSeconds(30);
}
// ─────────────────────────────────────────────────────────────────
// Main execution loop
// ─────────────────────────────────────────────────────────────────
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"SchedulerBackgroundService started. PollingIntervalSeconds={Interval}",
_pollingInterval.TotalSeconds);
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _services.CreateScope();
var taskManager = scope.ServiceProvider
.GetRequiredService();
// ✅ Build LoginDTO from config — includes ClientId for tenant isolation
var login = BuildLoginFromConfig();
// ✅ Pass stoppingToken so cycle respects graceful shutdown
var summary = await taskManager.ExecuteReadyTasksAsync(login, stoppingToken)
.ConfigureAwait(false);
_logger.LogInformation(
"SchedulerBackgroundService cycle complete | " +
"Total={Total} Published={Published} Skipped={Skipped} At={Time:HH:mm:ss}",
summary.Total, summary.Published, summary.Skipped, DateTime.UtcNow);
}
catch (OperationCanceledException)
{
// Graceful shutdown — exit loop cleanly
break;
}
catch (Exception ex)
{
// Log but do not crash the service — next cycle will retry
_logger.LogError(ex,
"SchedulerBackgroundService cycle failed at {Time:HH:mm:ss}",
DateTime.UtcNow);
}
try
{
await Task.Delay(_pollingInterval, stoppingToken).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
break;
}
}
_logger.LogInformation("SchedulerBackgroundService stopped.");
}
// ─────────────────────────────────────────────────────────────────
// LoginDTO resolution
// ─────────────────────────────────────────────────────────────────
///
/// Builds LoginDTO from appsettings.json SchedulerRun section.
/// ✅ ClientId is now set — all DAL queries use login.ClientId for @TenantId.
///
/// Required config keys:
/// SchedulerRun:DatabaseName — e.g. "GB5_PROD"
/// SchedulerRun:ClientId — e.g. "-1399999958" (TenantId in DB)
/// SchedulerRun:UserId — e.g. "-1" (system user)
///
private LoginDTO BuildLoginFromConfig()
{
var dbName = _config["SchedulerRun:DatabaseName"];
var clientId = _config["SchedulerRun:ClientId"];
var userId = _config["SchedulerRun:UserId"];
if (string.IsNullOrWhiteSpace(dbName))
throw new InvalidOperationException(
"SchedulerRun:DatabaseName is not configured in appsettings.");
if (string.IsNullOrWhiteSpace(clientId))
throw new InvalidOperationException(
"SchedulerRun:ClientId is not configured in appsettings. " +
"This is required for tenant isolation in all DAL queries.");
return new LoginDTO
{
DatabaseName = dbName,
ClientId = int.Parse(clientId),
UserId = string.IsNullOrWhiteSpace(userId) ? -1 : int.Parse(userId),
UserName = "SchedulerService"
};
}
}
}