using Hangfire; using Hangfire.SqlServer; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Polly; using Polly.Extensions.Http; using Stripe; using RealCV.Application.Interfaces; using RealCV.Infrastructure.Configuration; using RealCV.Infrastructure.Data; using RealCV.Infrastructure.ExternalApis; using RealCV.Infrastructure.Jobs; using RealCV.Infrastructure.Services; namespace RealCV.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { // Configure DbContext with SQL Server // AddDbContextFactory enables thread-safe parallel operations services.AddDbContextFactory(options => options.UseSqlServer( configuration.GetConnectionString("DefaultConnection"), sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); })); // Also register DbContext for scoped injection (non-parallel scenarios) services.AddDbContext(options => options.UseSqlServer( configuration.GetConnectionString("DefaultConnection"), sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); })); // Configure Hangfire with SQL Server storage services.AddHangfire(config => config .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseSqlServerStorage( configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, DisableGlobalLocks = true })); services.AddHangfireServer(); // Configure options services.Configure( configuration.GetSection(CompaniesHouseSettings.SectionName)); services.Configure( configuration.GetSection(AnthropicSettings.SectionName)); services.Configure( configuration.GetSection(AzureBlobSettings.SectionName)); services.Configure( configuration.GetSection(LocalStorageSettings.SectionName)); services.Configure( configuration.GetSection(StripeSettings.SectionName)); // Configure Stripe API key var stripeSettings = configuration.GetSection(StripeSettings.SectionName).Get(); if (!string.IsNullOrEmpty(stripeSettings?.SecretKey)) { StripeConfiguration.ApiKey = stripeSettings.SecretKey; } // Configure HttpClient for CompaniesHouseClient with retry policy services.AddHttpClient((serviceProvider, client) => { var settings = configuration .GetSection(CompaniesHouseSettings.SectionName) .Get(); if (settings is not null) { client.BaseAddress = new Uri(settings.BaseUrl); } }) .AddPolicyHandler(GetRetryPolicy()); // Register services services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Register file storage - use local storage if configured, otherwise Azure var useLocalStorage = configuration.GetValue("UseLocalStorage"); if (useLocalStorage) { services.AddScoped(); } else { services.AddScoped(); } // Register Hangfire jobs services.AddTransient(); services.AddTransient(); return services; } private static IAsyncPolicy GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests) .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (outcome, timespan, retryAttempt, context) => { // Logging could be added here via ILogger if injected }); } }