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;

/// <summary>
/// DI registration for the Notification Outbox component: binds
/// <see cref="NotificationOutboxOptions"/> and registers the channel delivery adapters.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>Configuration section bound to <see cref="NotificationOutboxOptions"/>.</summary>
    public const string OptionsSection = "ScadaBridge:NotificationOutbox";

    /// <summary>Configuration section bound to <see cref="SmsOptions"/>.</summary>
    public const string SmsOptionsSection = "ScadaBridge:Sms";

    /// <summary>
    /// Registers the Notification Outbox services: the <see cref="NotificationOutboxOptions"/>
    /// binding and the channel delivery adapters.
    ///
    /// This extension covers only the outbox-specific registrations. The
    /// <see cref="EmailNotificationDeliveryAdapter"/> reuses the
    /// <see cref="ZB.MOM.WW.ScadaBridge.NotificationService"/> SMTP machinery —
    /// <c>Func&lt;ISmtpClientWrapper&gt;</c>, <c>OAuth2TokenService</c>,
    /// <c>NotificationOptions</c> and — for configuration rows selecting the EWS transport —
    /// <c>IEwsMailSender</c> with its named HTTP client — so the caller (the Host on the
    /// central node) must also call <c>AddNotificationService()</c>. Re-registering those
    /// services here would duplicate them; relying on <c>AddNotificationService</c> keeps a
    /// single source of truth. Each is an optional constructor parameter on the adapter, so a
    /// host that skips <c>AddNotificationService()</c> still constructs — an EWS row then
    /// fails permanently ("no EWS sender is registered") rather than falling back to SMTP.
    ///
    /// <see cref="EmailNotificationDeliveryAdapter"/> is registered <em>scoped</em> because it
    /// takes a scoped <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories.INotificationRepository"/>
    /// directly. The <see cref="NotificationOutboxActor"/> 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 <see cref="SmsNotificationDeliveryAdapter"/> (Twilio-REST) is registered the same
    /// way alongside the Email adapter, so both resolve from
    /// <c>GetServices&lt;INotificationDeliveryAdapter&gt;()</c>. It also binds + validates
    /// <see cref="SmsOptions"/> (<c>ScadaBridge:Sms</c>) and the named <c>"Twilio"</c>
    /// <see cref="HttpClient"/>; unlike the Email adapter it needs no NotificationService
    /// machinery (a single outbound HTTPS POST per recipient).
    /// </summary>
    /// <param name="services">The DI service collection to register notification outbox services into.</param>
    /// <returns>The same <paramref name="services"/> instance for chaining.</returns>
    public static IServiceCollection AddNotificationOutbox(this IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(services);

        services.AddOptions<NotificationOutboxOptions>()
            .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<SmsOptions>()
            .BindConfiguration(SmsOptionsSection)
            .ValidateOnStart();
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IValidateOptions<SmsOptions>, 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<INotificationDeliveryAdapter>())
        // and as the concrete type (so callers and tests can resolve it directly).
        services.AddScoped<EmailNotificationDeliveryAdapter>();
        services.AddScoped<INotificationDeliveryAdapter>(
            sp => sp.GetRequiredService<EmailNotificationDeliveryAdapter>());

        services.AddScoped<SmsNotificationDeliveryAdapter>();
        services.AddScoped<INotificationDeliveryAdapter>(
            sp => sp.GetRequiredService<SmsNotificationDeliveryAdapter>());

        // 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<IKpiSampleSource, NotificationOutboxKpiSampleSource>());

        return services;
    }
}
