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

namespace ZB.MOM.WW.Auth.AspNetCore.Tests;

public class ServiceCollectionExtensionsTests
{
    private const string LdapSection = "Auth:Ldap";

    private const string LdapServer = "ldap.example.com";

    private static IConfiguration BuildConfiguration() =>
        new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                [$"{LdapSection}:Server"] = LdapServer,
                [$"{LdapSection}:SearchBase"] = "dc=example,dc=com",
                [$"{LdapSection}:ServiceAccountDn"] = "cn=svc,dc=example,dc=com",
                [$"{LdapSection}:Transport"] = nameof(LdapTransport.Ldaps),
            })
            .Build();

    [Fact]
    public void AddZbLdapAuth_ResolvesLdapAuthService()
    {
        IConfiguration config = BuildConfiguration();
        var services = new ServiceCollection();

        services.AddZbLdapAuth(config, LdapSection);

        using ServiceProvider provider = services.BuildServiceProvider();

        var service = provider.GetRequiredService<ILdapAuthService>();

        Assert.NotNull(service);
    }

    [Fact]
    public void AddZbLdapAuth_ILdapAuthService_IsSingleton()
    {
        IConfiguration config = BuildConfiguration();
        var services = new ServiceCollection();

        services.AddZbLdapAuth(config, LdapSection);

        using ServiceProvider provider = services.BuildServiceProvider();

        var first = provider.GetRequiredService<ILdapAuthService>();
        var second = provider.GetRequiredService<ILdapAuthService>();

        Assert.Same(first, second);
    }

    [Fact]
    public void AddZbLdapAuth_BindsOptionsFromSection()
    {
        IConfiguration config = BuildConfiguration();
        var services = new ServiceCollection();

        services.AddZbLdapAuth(config, LdapSection);

        using ServiceProvider provider = services.BuildServiceProvider();
        var options = provider.GetRequiredService<IOptions<LdapOptions>>();

        Assert.Equal(LdapServer, options.Value.Server);
        Assert.Equal("dc=example,dc=com", options.Value.SearchBase);
        Assert.Equal(LdapTransport.Ldaps, options.Value.Transport);
    }

    [Fact]
    public void AddZbLdapAuth_RegistersOptionsValidator()
    {
        IConfiguration config = BuildConfiguration();
        var services = new ServiceCollection();

        services.AddZbLdapAuth(config, LdapSection);

        using ServiceProvider provider = services.BuildServiceProvider();

        var validators = provider.GetServices<IValidateOptions<LdapOptions>>().ToList();

        Assert.Contains(validators, v => v is LdapOptionsValidator);
    }

    // --- Auth-001: ValidateOnStart must run options validation at host startup, not first login ---

    private static IConfiguration BuildInsecureConfiguration() =>
        new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                [$"{LdapSection}:Server"] = LdapServer,
                [$"{LdapSection}:SearchBase"] = "dc=example,dc=com",
                [$"{LdapSection}:ServiceAccountDn"] = "cn=svc,dc=example,dc=com",
                // Plaintext transport without AllowInsecure: the validator must reject this.
                [$"{LdapSection}:Transport"] = nameof(LdapTransport.None),
                [$"{LdapSection}:AllowInsecure"] = "false",
            })
            .Build();

    [Fact]
    public async Task AddZbLdapAuth_StartingHost_FailsForInsecureConfig()
    {
        // The misconfiguration must surface at host start, not deferred until the first login
        // (i.e. the first ILdapAuthService resolution). ValidateOnStart wires the host's
        // start-time options validation, so StartAsync must throw OptionsValidationException.
        IConfiguration config = BuildInsecureConfiguration();

        using IHost host = new HostBuilder()
            .ConfigureServices(services => services.AddZbLdapAuth(config, LdapSection))
            .Build();

        OptionsValidationException ex =
            await Assert.ThrowsAsync<OptionsValidationException>(() => host.StartAsync());

        Assert.Contains(nameof(LdapOptions.Transport), string.Join(" ", ex.Failures));
    }

    [Fact]
    public async Task AddZbLdapAuth_StartingHost_SucceedsForSecureConfig()
    {
        // A valid (secure) config must start cleanly — proving ValidateOnStart does not reject
        // well-formed options.
        IConfiguration config = BuildConfiguration();

        using IHost host = new HostBuilder()
            .ConfigureServices(services => services.AddZbLdapAuth(config, LdapSection))
            .Build();

        await host.StartAsync();
        await host.StopAsync();
    }
}
