using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.Ldap;
using ZB.MOM.WW.Auth.Ldap;

namespace ZB.MOM.WW.Auth.AspNetCore;

/// <summary>
/// Dependency-injection helpers that wire up the ZB.MOM.WW LDAP authentication provider
/// from configuration. Composes the concrete implementation living in the
/// <c>ZB.MOM.WW.Auth.Ldap</c> package so consuming apps register a provider with a single call.
/// </summary>
/// <remarks>
/// API-key DI wiring lives in <c>ZB.MOM.WW.Auth.ApiKeys</c>
/// (<c>ZB.MOM.WW.Auth.ApiKeys.DependencyInjection.ApiKeyServiceCollectionExtensions.AddZbApiKeyAuth</c>)
/// so that an LDAP-only consumer can reference this package without pulling in SQLite.
/// </remarks>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Registers LDAP authentication: binds and validates <see cref="LdapOptions"/> from the
    /// configuration section at <paramref name="sectionPath"/>, and registers
    /// <see cref="ILdapAuthService"/>.
    /// </summary>
    /// <param name="services">The service collection to add to.</param>
    /// <param name="config">The application configuration.</param>
    /// <param name="sectionPath">Path of the configuration section holding the LDAP options.</param>
    /// <returns>The same <paramref name="services"/> instance, for chaining.</returns>
    public static IServiceCollection AddZbLdapAuth(
        this IServiceCollection services,
        IConfiguration config,
        string sectionPath)
    {
        ArgumentNullException.ThrowIfNull(services);
        ArgumentNullException.ThrowIfNull(config);
        ArgumentException.ThrowIfNullOrWhiteSpace(sectionPath);

        // Bind via the options builder and opt into start-time validation. An IValidateOptions<T>
        // otherwise only runs when the options are first materialized (IOptions<T>.Value) — which
        // here is the first login (ILdapAuthService factory below), not boot. ValidateOnStart hooks
        // the host's start-time options validation so a misconfigured directory (e.g. insecure
        // transport without AllowInsecure) fails fast at startup rather than on first login.
        services.AddOptions<LdapOptions>()
            .Bind(config.GetSection(sectionPath))
            .ValidateOnStart();

        // Fail fast at startup on a misconfigured directory rather than on first login.
        services.AddSingleton<IValidateOptions<LdapOptions>, LdapOptionsValidator>();

        // LdapAuthService is stateless: it holds only a snapshot of LdapOptions and a stateless
        // connection factory, and opens/disposes a connection per call. It is not IDisposable.
        // Singleton is correct; TryAdd mirrors the pattern in AddZbApiKeyAuth (idempotency).
        services.TryAddSingleton<ILdapAuthService>(sp =>
            new LdapAuthService(sp.GetRequiredService<IOptions<LdapOptions>>().Value));

        return services;
    }
}
