using Eis.Infrastructure.Authentication;
using Eis.Infrastructure.Data;
using Microsoft.Data.Sqlite;

namespace Eis.Infrastructure.Tests.Data;

public sealed class DatabaseInitializerTests
{
    [Fact]
    public async Task EmbeddedSchemas_AreNativeSqlResources()
    {
        var assembly = typeof(DatabaseInitializer).Assembly;
        var resourceNames = assembly.GetManifestResourceNames();
        var sqliteName = Assert.Single(resourceNames, name => name.EndsWith("Schema.sqlite.sql", StringComparison.Ordinal));
        var mysqlName = Assert.Single(resourceNames, name => name.EndsWith("Schema.mysql.sql", StringComparison.Ordinal));

        var sqlite = await ReadResourceAsync(assembly, sqliteName);
        var mysql = (await ReadResourceAsync(assembly, mysqlName)).ReplaceLineEndings("\n");
        var mysqlStatements = mysql.Split(
            "\n-- EIS-STATEMENT\n",
            StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        Assert.Contains("CREATE TABLE IF NOT EXISTS schema_metadata", sqlite, StringComparison.Ordinal);
        Assert.DoesNotContain("export const", sqlite, StringComparison.Ordinal);
        Assert.True(mysqlStatements.Length >= 30);
        Assert.All(mysqlStatements, statement =>
            Assert.StartsWith("CREATE TABLE IF NOT EXISTS", statement, StringComparison.Ordinal));
        Assert.Contains("`separator` VARCHAR(10)", mysql, StringComparison.Ordinal);
        Assert.DoesNotContain("export const", mysql, StringComparison.Ordinal);
    }

    [Fact]
    public async Task InitializeAsync_CreatesFreshDatabaseAndIsIdempotent()
    {
        var root = Path.Combine(Path.GetTempPath(), $"eis-fresh-init-{Guid.NewGuid():N}");
        var path = Path.Combine(root, "eis.sqlite");
        Directory.CreateDirectory(root);
        try
        {
            var options = DatabaseOptions.CreateSqliteForTests(path);
            var factory = new RelationalConnectionFactory(options);
            var passwords = new PasswordCompatibilityService();
            var initializer = new DatabaseInitializer(factory, options, passwords);

            await initializer.InitializeAsync(CancellationToken.None);
            await initializer.InitializeAsync(CancellationToken.None);

            await using var connection = new SqliteConnection($"Data Source={path}");
            await connection.OpenAsync();
            Assert.True(await ScalarAsync(connection,
                "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'") >= 32);
            Assert.Equal(20, await ScalarAsync(connection,
                "SELECT schema_version FROM schema_metadata WHERE id = 1"));
            Assert.Equal(1, await ScalarAsync(connection,
                "SELECT COUNT(*) FROM users WHERE id = 'usr_admin' AND role = 'admin' AND admin_level = 'super'"));
            Assert.Equal(5, await ScalarAsync(connection,
                "SELECT COUNT(*) FROM workflow_definitions WHERE active = 1"));
            Assert.Equal(4, await ScalarAsync(connection,
                "SELECT COUNT(*) FROM admission_number_rules WHERE active = 1"));
            await connection.DisposeAsync();
            SqliteConnection.ClearAllPools();
        }
        finally
        {
            if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
        }
    }

    private static async Task<long> ScalarAsync(SqliteConnection connection, string sql)
    {
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        return Convert.ToInt64(await command.ExecuteScalarAsync());
    }

    private static async Task<string> ReadResourceAsync(System.Reflection.Assembly assembly, string name)
    {
        await using var stream = assembly.GetManifestResourceStream(name)
                                 ?? throw new InvalidOperationException($"Missing resource: {name}");
        using var reader = new StreamReader(stream);
        return await reader.ReadToEndAsync();
    }
}
