using BitBadger.Documents.Postgres;
using Npgsql;
using Npgsql.FSharp;
using ThrowawayDb.Postgres;

namespace BitBadger.Documents.Tests;

/// <summary>
/// A throwaway SQLite database file, which will be deleted when it goes out of scope
/// </summary>
public class ThrowawayPostgresDb : IDisposable, IAsyncDisposable
{
    private readonly ThrowawayDatabase _db;

    /// <summary>
    /// The connection string for the throwaway database
    /// </summary>
    public string ConnectionString => _db.ConnectionString;
    
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="db">The throwaway database which this instance will wrap</param>
    public ThrowawayPostgresDb(ThrowawayDatabase db)
    {
        _db = db;
    }
    
    public void Dispose()
    {
        _db.Dispose();
        GC.SuppressFinalize(this);
    }

    public ValueTask DisposeAsync()
    {
        _db.Dispose();
        GC.SuppressFinalize(this);
        return ValueTask.CompletedTask;
    }
}

/// <summary>
/// Database helpers for PostgreSQL integration tests
/// </summary>
public static class PostgresDb
{
    /// <summary>
    /// The name of the table used for testing
    /// </summary>
    public const string TableName = "test_table";

    /// <summary>
    /// The host for the database
    /// </summary>
    private static readonly Lazy<string> DbHost = new(() =>
        Environment.GetEnvironmentVariable("BBDOX_PG_HOST") switch
        {
            null => "localhost",
            var host when host.Trim() == "" => "localhost",
            var host => host
        });

    /// <summary>
    /// The port for the database
    /// </summary>
    private static readonly Lazy<int> DbPort = new(() =>
        Environment.GetEnvironmentVariable("BBDOX_PG_PORT") switch
        {
            null => 5432,
            var port when port.Trim() == "" => 5432,
            var port => int.Parse(port)
        });

    /// <summary>
    /// The database itself
    /// </summary>
    private static readonly Lazy<string> DbDatabase = new(() =>
        Environment.GetEnvironmentVariable("BBDOX_PG_DATABASE") switch
        {
            null => "postgres",
            var db when db.Trim() == "" => "postgres",
            var db => db
        });

    /// <summary>
    /// The user to use in connecting to the database
    /// </summary>
    private static readonly Lazy<string> DbUser = new(() =>
        Environment.GetEnvironmentVariable("BBDOX_PG_USER") switch
        {
            null => "postgres",
            var user when user.Trim() == "" => "postgres",
            var user => user
        });

    /// <summary>
    /// The password to use for the database
    /// </summary>
    private static readonly Lazy<string> DbPassword = new(() =>
        Environment.GetEnvironmentVariable("BBDOX_PG_PWD") switch
        {
            null => "postgres",
            var pwd when pwd.Trim() == "" => "postgres",
            var pwd => pwd
        });

    /// <summary>
    /// The overall connection string
    /// </summary>
    public static readonly Lazy<string> ConnStr = new(() =>
        Sql.formatConnectionString(
            Sql.password(DbPassword.Value,
                Sql.username(DbUser.Value,
                    Sql.database(DbDatabase.Value,
                        Sql.port(DbPort.Value,
                            Sql.host(DbHost.Value)))))));

    /// <summary>
    /// Create a data source using the derived connection string
    /// </summary>
    public static NpgsqlDataSource MkDataSource(string cStr) =>
        new NpgsqlDataSourceBuilder(cStr).Build();

    /// <summary>
    /// Build the throwaway database
    /// </summary>
    public static ThrowawayPostgresDb BuildDb()
    {
        var database = ThrowawayDatabase.Create(ConnStr.Value);

        var sqlProps = Sql.connect(database.ConnectionString);

        Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
        Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName, Dialect.PostgreSQL), sqlProps));

        Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));

        return new ThrowawayPostgresDb(database);
    }
}