using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox;
///
/// DI registration for the Notification Outbox component: binds
/// and registers the channel delivery adapters.
///
public static class ServiceCollectionExtensions
{
/// Configuration section bound to .
public const string OptionsSection = "ScadaBridge:NotificationOutbox";
/// Configuration section bound to .
public const string SmsOptionsSection = "ScadaBridge:Sms";
///
/// Registers the Notification Outbox services: the
/// binding and the channel delivery adapters.
///
/// This extension covers only the outbox-specific registrations. The
/// reuses the
/// SMTP machinery —
/// Func<ISmtpClientWrapper>, OAuth2TokenService and
/// NotificationOptions — so the caller (the Host on the central node) must also
/// call AddNotificationService(). Re-registering those services here would
/// duplicate them; relying on AddNotificationService keeps a single source of truth.
///
/// is registered scoped because it
/// takes a scoped
/// directly. The resolves the adapters from a fresh
/// scope per dispatch sweep rather than holding them, so no scoped adapter is captured by
/// the singleton actor.
///
/// The (Twilio-REST) is registered the same
/// way alongside the Email adapter, so both resolve from
/// GetServices<INotificationDeliveryAdapter>(). It also binds + validates
/// (ScadaBridge:Sms) and the named "Twilio"
/// ; unlike the Email adapter it needs no NotificationService
/// machinery (a single outbound HTTPS POST per recipient).
///
/// The DI service collection to register notification outbox services into.
/// The same instance for chaining.
public static IServiceCollection AddNotificationOutbox(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.AddOptions()
.BindConfiguration(OptionsSection);
// Bind + validate SmsOptions (ScadaBridge:Sms): the SMS adapter's body cap and
// per-request timeout fallback. ValidateOnStart surfaces a bad option at host
// startup rather than at first delivery; the validator is registered via
// TryAddEnumerable so a re-call of AddNotificationOutbox does not double-register it.
services.AddOptions()
.BindConfiguration(SmsOptionsSection)
.ValidateOnStart();
services.TryAddEnumerable(
ServiceDescriptor.Singleton, SmsOptionsValidator>());
// Named HTTP client backing the Twilio-REST SMS adapter. Pooled handler lifetime
// is managed by IHttpClientFactory; the adapter applies the per-request timeout
// via a linked CTS (so the client's own Timeout stays at its default).
services.AddHttpClient(SmsNotificationDeliveryAdapter.HttpClientName);
// Scoped: the adapters hold a scoped INotificationRepository. Each is registered
// both under the interface (so the dispatch sweep can enumerate every channel
// adapter — both Email and SMS must resolve from GetServices())
// and as the concrete type (so callers and tests can resolve it directly).
services.AddScoped();
services.AddScoped(
sp => sp.GetRequiredService());
services.AddScoped();
services.AddScoped(
sp => sp.GetRequiredService());
// KPI history: the recorder singleton enumerates every IKpiSampleSource each
// sampling pass to snapshot the outbox delivery KPIs into the central history store.
// TryAddEnumerable is idempotent — no double-registration if AddNotificationOutbox
// is ever called more than once, matching the AuditLog idiom.
services.TryAddEnumerable(ServiceDescriptor.Scoped());
return services;
}
}