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

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

/// <summary>
/// Container-resolution tests for both SQL-Server topologies.
/// </summary>
/// <remarks>
/// These exist because a code review caught that neither mode could resolve at all: the decorators
/// ask for the concrete <see cref="SqliteSecretStore"/>, which the core package only registered
/// behind <see cref="ISecretStore"/>. Every unit test passed and both modes were dead on arrival,
/// because nothing built a provider. Resolving the graph is the assertion that matters.
/// </remarks>
public sealed class AddZbSecretsSqlServerTests
{
    private static IConfiguration Config(string sqlitePath) =>
        new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["Secrets:SqlitePath"] = sqlitePath,
            ["Secrets:RunMigrationsOnStartup"] = "false",
            ["Secrets:MasterKey:Source"] = "Environment",
            ["Secrets:MasterKey:EnvVarName"] = "ZB_SECRETS_TEST_KEY",
            ["Secrets:SqlServer:ConnectionString"] = "Server=unused;Database=x;",
        }).Build();

    private static string TempDb() => Path.Combine(Path.GetTempPath(), $"zb-di-{Guid.NewGuid():N}.db");

    [Fact]
    public void SharedStore_mode_resolves_the_SqlServer_store_as_ISecretStore()
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddZbSecretsSqlServerStore(Config(TempDb()));

        using ServiceProvider provider = services.BuildServiceProvider();

        // The SQL-Server store displaces SQLite — no decorator, because there is nothing to replicate.
        Assert.IsType<SqlServerSecretStore>(provider.GetRequiredService<ISecretStore>());
        Assert.IsType<SqlServerSecretsStoreMigrator>(provider.GetRequiredService<ISecretsStoreMigrator>());
    }

    [Fact]
    public void HubReplication_mode_resolves_a_decorated_local_store()
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddZbSecretsSqlServerReplication(Config(TempDb()));

        using ServiceProvider provider = services.BuildServiceProvider();

        Assert.IsType<ReplicatingSecretStore>(provider.GetRequiredService<ISecretStore>());
        Assert.IsType<SqlServerSecretReplicator>(provider.GetRequiredService<ISecretReplicator>());
        // The local store is still provisioned by the core SQLite migrator, not the hub's.
        Assert.IsType<SqliteSecretsStoreMigrator>(provider.GetRequiredService<ISecretsStoreMigrator>());
    }

    [Fact]
    public void HubReplication_mode_registers_both_the_hub_migration_and_the_sync_service()
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddZbSecretsSqlServerReplication(Config(TempDb()));

        using ServiceProvider provider = services.BuildServiceProvider();
        IHostedService[] hosted = [.. provider.GetServices<IHostedService>()];

        // The hub schema is nobody's "own" store, so every node must provision it at startup too.
        Assert.Contains(hosted, h => h is SqlServerSecretSyncService);
        Assert.Single(hosted, h => h.GetType().Name == "SqlServerHubMigrationHostedService");
    }

    [Fact]
    public void The_decorator_wraps_the_undecorated_local_store_rather_than_itself()
    {
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddZbSecretsSqlServerReplication(Config(TempDb()));

        using ServiceProvider provider = services.BuildServiceProvider();

        // Guards against the self-referential registration that would stack-overflow on first use.
        var decorated = (ReplicatingSecretStore)provider.GetRequiredService<ISecretStore>();
        Assert.NotNull(decorated);
        Assert.NotSame(decorated, provider.GetRequiredService<SqliteSecretStore>());
    }

    [Fact]
    public void A_missing_connection_string_fails_at_registration_not_at_first_resolve()
    {
        IConfiguration config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Secrets:SqlitePath"] = TempDb(),
            })
            .Build();

        var services = new ServiceCollection();
        services.AddLogging();

        InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
            () => services.AddZbSecretsSqlServerStore(config));

        Assert.Contains("ConnectionString", ex.Message, StringComparison.Ordinal);
    }
}
