using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Configuration;

namespace ZB.MOM.WW.Configuration.Tests;

public sealed class AddValidatedOptionsTests
{
    private sealed class NodeOptions { public int Port { get; set; } public string? Name { get; set; } }

    private sealed class NodeValidator : OptionsValidatorBase<NodeOptions>
    {
        protected override void Validate(ValidationBuilder v, NodeOptions o)
        {
            v.Port(o.Port, "Node:Port");
            v.Required(o.Name, "Node:Name");
        }
    }

    private static IHost BuildHost(Dictionary<string, string?> config)
    {
        var builder = Host.CreateApplicationBuilder();
        builder.Configuration.AddInMemoryCollection(config);
        builder.Services.AddValidatedOptions<NodeOptions, NodeValidator>(builder.Configuration, "Node");
        return builder.Build();
    }

    [Fact]
    public async Task Bad_config_throws_at_startup()
    {
        using var host = BuildHost(new() { ["Node:Port"] = "0", ["Node:Name"] = "" });
        await Assert.ThrowsAsync<OptionsValidationException>(() => host.StartAsync());
    }

    [Fact]
    public async Task Good_config_starts_and_binds()
    {
        using var host = BuildHost(new() { ["Node:Port"] = "8080", ["Node:Name"] = "central" });
        await host.StartAsync();
        var opts = host.Services.GetRequiredService<IOptions<NodeOptions>>().Value;
        Assert.Equal(8080, opts.Port);
        Assert.Equal("central", opts.Name);
        await host.StopAsync();
    }

    [Fact]
    public void Calling_twice_registers_validator_once()
    {
        var config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?> { ["Node:Port"] = "0", ["Node:Name"] = "" })
            .Build();
        var services = new ServiceCollection();
        services.AddValidatedOptions<NodeOptions, NodeValidator>(config, "Node");
        services.AddValidatedOptions<NodeOptions, NodeValidator>(config, "Node");

        using var provider = services.BuildServiceProvider();
        var validators = provider.GetServices<IValidateOptions<NodeOptions>>().ToArray();
        Assert.Single(validators);

        // Resolving the options surfaces each accumulated failure exactly once, not doubled.
        var ex = Assert.Throws<OptionsValidationException>(
            () => provider.GetRequiredService<IOptions<NodeOptions>>().Value);
        Assert.Equal(2, ex.Failures.Count());
    }
}
