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" }; } } }