using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.DependencyInjection;
using ZB.MOM.WW.Secrets.Replication;
using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.DependencyInjection;
using ZB.MOM.WW.Secrets.Sqlite;

namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Tests.DependencyInjection;

/// <summary>
/// Registration tests for <c>AddZbSecretsAkkaReplication</c>.
/// <para>
/// These exist because 0.2.0 shipped with Akka replication silently inert and every other test in
/// this assembly still passed. The bug was pure registration ORDER: the extension called
/// <c>AddZbSecrets</c> first, which does
/// <c>TryAddSingleton&lt;ISecretReplicator, NoOpSecretReplicator&gt;()</c>, so the package's own
/// <c>TryAddSingleton&lt;ISecretReplicator&gt;</c> found a descriptor already present and was
/// silently discarded. Writes then published into a no-op sink and no replication actor was ever
/// spawned — with no exception and no log line anywhere.
/// </para>
/// <para>
/// The equivalent SQL-Server package always had a test asserting its replicator type, and always
/// had the correct order. That asymmetry is exactly why the defect landed in one package and not
/// the other, so the gap is closed here.
/// </para>
/// <para>
/// These assert against the <see cref="IServiceCollection"/> because the 0.2.0 defect was entirely
/// about which descriptor wins, and the descriptor is where it is visible. Resolution through a
/// BUILT provider — where the scadaproj#1 singleton-cycle deadlock lived — is covered by
/// <see cref="HostedProcessResolutionTests"/>, and end-to-end actor behaviour by
/// <c>TwoNodeClusterReplicationTests</c>. (An earlier revision of this comment claimed a
/// provider-based test was impossible because <c>DistributedPubSub.Get</c> needs a joined
/// cluster; the hang it was avoiding was actually that DI deadlock, and a single self-joined
/// node is all the cluster the actor needs.)
/// </para>
/// </summary>
public sealed class AddZbSecretsAkkaReplicationTests
{
    private static IServiceCollection Register()
    {
        IConfigurationRoot config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Secrets:SqlitePath"] = Path.Combine(Path.GetTempPath(), $"zb-akkadi-{Guid.NewGuid():N}.db"),
                ["Secrets:Replication:AnnounceInterval"] = "00:00:30",
            })
            .Build();

        var services = new ServiceCollection();
        services.AddLogging();
        services.AddZbSecretsAkkaReplication(config, "Secrets", "Secrets:Replication");
        return services;
    }

    private static ServiceDescriptor Descriptor<T>(IServiceCollection services) =>
        services.Single(d => d.ServiceType == typeof(T));

    [Fact]
    public void ISecretReplicator_is_not_bound_to_the_no_op_sink()
    {
        // THE REGRESSION TEST. Before the fix this descriptor was NoOpSecretReplicator, so every
        // replicated write went nowhere. A no-op secret replicator is the worst failure mode
        // available: the cluster reports healthy and silently never converges.
        ServiceDescriptor descriptor = Descriptor<ISecretReplicator>(Register());

        Assert.NotEqual(typeof(NoOpSecretReplicator), descriptor.ImplementationType);
    }

    [Fact]
    public void ISecretReplicator_is_registered_by_this_package_via_a_factory()
    {
        // The package registers through a factory (it needs the lazily-created actor ref), whereas
        // the core no-op is registered by concrete type. A factory registration is therefore proof
        // that this package's descriptor won the TryAdd race rather than core's.
        ServiceDescriptor descriptor = Descriptor<ISecretReplicator>(Register());

        Assert.NotNull(descriptor.ImplementationFactory);
        Assert.Null(descriptor.ImplementationType);
    }

    [Fact]
    public void ISecretStore_is_decorated_so_writes_actually_publish()
    {
        // AddSingleton (not TryAdd) means the decorator is appended, and the LAST registration for
        // a service type is what the container resolves. If this regressed to the bare SQLite
        // store, writes would never publish.
        ServiceDescriptor last = Register()
            .Last(d => d.ServiceType == typeof(ISecretStore));

        Assert.NotNull(last.ImplementationFactory);
    }

    [Fact]
    public void The_undecorated_concrete_store_is_registered()
    {
        // ReplicatingSecretStore resolves SqliteSecretStore by concrete type. Core registering it
        // only behind ISecretStore was the OTHER defect this library shipped, so pin it here too.
        IServiceCollection services = Register();

        Assert.Contains(services, d => d.ServiceType == typeof(SqliteSecretStore));
    }

    [Fact]
    public void The_store_migrator_seam_is_registered()
    {
        IServiceCollection services = Register();

        Assert.Contains(services, d => d.ServiceType == typeof(ISecretsStoreMigrator));
    }
}
