using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
using ZB.MOM.WW.Auth.ApiKeys.DependencyInjection;

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

public class ApiKeyServiceCollectionExtensionsTests
{
    private const string ApiKeySection = "Auth:ApiKeys";

    private const string PepperSecretName = "ApiKeyPepper";
    private const string PepperValue = "super-secret-pepper-value";

    private static IConfiguration BuildConfiguration(string sqlitePath, bool runMigrationsOnStartup = false) =>
        new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                [$"{ApiKeySection}:TokenPrefix"] = "mxgw",
                [$"{ApiKeySection}:SqlitePath"] = sqlitePath,
                [$"{ApiKeySection}:PepperSecretName"] = PepperSecretName,
                [$"{ApiKeySection}:RunMigrationsOnStartup"] = runMigrationsOnStartup ? "true" : "false",

                // The pepper itself lives at the top level under the configured secret name.
                [PepperSecretName] = PepperValue,
            })
            .Build();

    private static string TempSqlitePath() =>
        Path.Combine(Path.GetTempPath(), $"zbauth-test-{Guid.NewGuid():N}.db");

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

        services.AddZbApiKeyAuth(config, ApiKeySection);

        using ServiceProvider provider = services.BuildServiceProvider();

        var verifier = provider.GetRequiredService<IApiKeyVerifier>();

        Assert.NotNull(verifier);
    }

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

        services.AddZbApiKeyAuth(config, ApiKeySection);

        using ServiceProvider provider = services.BuildServiceProvider();

        Assert.NotNull(provider.GetRequiredService<IApiKeyStore>());
        Assert.NotNull(provider.GetRequiredService<IApiKeyAdminStore>());
        Assert.NotNull(provider.GetRequiredService<IApiKeyAuditStore>());
    }

    [Fact]
    public void AddZbApiKeyAuth_BindsOptionsFromSection()
    {
        string sqlitePath = TempSqlitePath();
        IConfiguration config = BuildConfiguration(sqlitePath);
        var services = new ServiceCollection();

        services.AddZbApiKeyAuth(config, ApiKeySection);

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

        Assert.Equal("mxgw", options.Value.TokenPrefix);
        Assert.Equal(sqlitePath, options.Value.SqlitePath);
        Assert.Equal(PepperSecretName, options.Value.PepperSecretName);
    }

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

        services.AddZbApiKeyAuth(config, ApiKeySection);

        using ServiceProvider provider = services.BuildServiceProvider();
        var pepperProvider = provider.GetRequiredService<IApiKeyPepperProvider>();

        Assert.IsType<ConfigurationApiKeyPepperProvider>(pepperProvider);
        Assert.Equal(PepperValue, pepperProvider.GetPepper());
    }

    [Fact]
    public void ConfigurationApiKeyPepperProvider_ReturnsNull_WhenSecretNameUnset()
    {
        IConfiguration config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>())
            .Build();
        var options = Options.Create(new ApiKeyOptions { PepperSecretName = "" });

        var provider = new ConfigurationApiKeyPepperProvider(config, options);

        Assert.Null(provider.GetPepper());
    }

    [Fact]
    public void ConfigurationApiKeyPepperProvider_ReturnsNull_WhenValueAbsent()
    {
        IConfiguration config = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>())
            .Build();
        var options = Options.Create(new ApiKeyOptions { PepperSecretName = "Missing" });

        var provider = new ConfigurationApiKeyPepperProvider(config, options);

        Assert.Null(provider.GetPepper());
    }

    [Fact]
    public async Task AddZbApiKeyAuth_MigrationHostedService_CreatesSchemaOnStartup()
    {
        string sqlitePath = TempSqlitePath();
        try
        {
            IConfiguration config = BuildConfiguration(sqlitePath, runMigrationsOnStartup: true);
            var services = new ServiceCollection();
            services.AddZbApiKeyAuth(config, ApiKeySection);

            await using ServiceProvider provider = services.BuildServiceProvider();

            // Find the ApiKeyMigrationHostedService among all registered IHostedService instances.
            var hostedServices = provider.GetServices<IHostedService>().ToList();
            IHostedService? migrationService = hostedServices
                .FirstOrDefault(s => s.GetType().Name == "ApiKeyMigrationHostedService");

            Assert.NotNull(migrationService);

            await migrationService!.StartAsync(CancellationToken.None);

            // Verify the api_keys table was created by the migration.
            string connectionString = new SqliteConnectionStringBuilder
            {
                DataSource = sqlitePath,
                Mode = SqliteOpenMode.ReadOnly,
            }.ToString();

            await using var connection = new SqliteConnection(connectionString);
            await connection.OpenAsync();

            await using var command = connection.CreateCommand();
            command.CommandText = """
                SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = 'api_keys';
                """;

            long tableCount = (long)(await command.ExecuteScalarAsync() ?? 0L);

            Assert.Equal(1L, tableCount);
        }
        finally
        {
            if (File.Exists(sqlitePath))
                File.Delete(sqlitePath);
        }
    }

    [Fact]
    public async Task AddZbApiKeyAuth_MigrationHostedService_SkipsMigration_WhenRunMigrationsOnStartupFalse()
    {
        string sqlitePath = TempSqlitePath();
        try
        {
            IConfiguration config = BuildConfiguration(sqlitePath, runMigrationsOnStartup: false);
            var services = new ServiceCollection();
            services.AddZbApiKeyAuth(config, ApiKeySection);

            await using ServiceProvider provider = services.BuildServiceProvider();

            var hostedServices = provider.GetServices<IHostedService>().ToList();
            IHostedService? migrationService = hostedServices
                .FirstOrDefault(s => s.GetType().Name == "ApiKeyMigrationHostedService");

            Assert.NotNull(migrationService);

            // StartAsync should complete without creating the database file.
            await migrationService!.StartAsync(CancellationToken.None);

            Assert.False(File.Exists(sqlitePath),
                "Migration should not run when RunMigrationsOnStartup is false.");
        }
        finally
        {
            if (File.Exists(sqlitePath))
                File.Delete(sqlitePath);
        }
    }
}
