using Microsoft.Data.Sqlite;
using ZB.MOM.WW.LocalDb.Internal;

namespace ZB.MOM.WW.LocalDb.Tests;

public sealed class LocalDbSchemaTests : IDisposable
{
    private readonly string _path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".db");

    public void Dispose()
    {
        SqliteConnection.ClearAllPools();
        if (File.Exists(_path))
            File.Delete(_path);
    }

    private SqliteConnection Open()
    {
        var conn = new SqliteConnection($"Data Source={_path}");
        conn.Open();
        return conn;
    }

    private static long ScalarLong(SqliteConnection conn, string sql)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = sql;
        return Convert.ToInt64(cmd.ExecuteScalar());
    }

    private static string ScalarString(SqliteConnection conn, string sql)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = sql;
        return (string)cmd.ExecuteScalar()!;
    }

    [Fact]
    public void EnsureCreated_CreatesAllObjects()
    {
        using var conn = Open();
        LocalDbSchema.EnsureCreated(conn);

        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table'";
        var tables = new List<string>();
        using (var reader = cmd.ExecuteReader())
            while (reader.Read())
                tables.Add(reader.GetString(0));

        foreach (var expected in new[]
                 {
                     "__localdb_meta", "__localdb_oplog", "__localdb_peer_state",
                     "__localdb_row_version", "__localdb_applying", "__localdb_dead_letter"
                 })
            Assert.Contains(expected, tables);

        Assert.Equal(1, ScalarLong(conn,
            "SELECT count(*) FROM sqlite_master WHERE type='index' AND name='__localdb_oplog_hlc'"));
    }

    [Fact]
    public void EnsureCreated_IsIdempotent()
    {
        using var conn = Open();
        LocalDbSchema.EnsureCreated(conn);
        LocalDbSchema.EnsureCreated(conn);

        Assert.Equal(1, ScalarLong(conn, "SELECT COUNT(*) FROM __localdb_meta"));
        Assert.Equal(1, ScalarLong(conn, "SELECT COUNT(*) FROM __localdb_applying"));
        Assert.Equal(1, ScalarLong(conn, "SELECT COUNT(*) FROM __localdb_peer_state"));
    }

    [Fact]
    public void Meta_NodeId_MintedOnceAndStable()
    {
        string first;
        using (var conn = Open())
        {
            LocalDbSchema.EnsureCreated(conn);
            first = ScalarString(conn, "SELECT node_id FROM __localdb_meta WHERE id=1");
        }

        using (var conn = Open())
        {
            LocalDbSchema.EnsureCreated(conn);
            var second = ScalarString(conn, "SELECT node_id FROM __localdb_meta WHERE id=1");
            Assert.Equal(first, second);
        }

        Assert.True(Guid.TryParse(first, out _));
    }

    [Fact]
    public void Applying_SeededWithZeroRow()
    {
        using var conn = Open();
        LocalDbSchema.EnsureCreated(conn);

        Assert.Equal(0, ScalarLong(conn, "SELECT applying FROM __localdb_applying WHERE id=1"));
    }

    [Fact]
    public void ReadMeta_ReturnsNodeIdAndHighWater()
    {
        using var conn = Open();
        LocalDbSchema.EnsureCreated(conn);

        var (nodeId, highWater) = LocalDbSchema.ReadMeta(conn);

        Assert.True(Guid.TryParse(nodeId, out _));
        Assert.Equal(0, highWater);
    }

    [Fact]
    public void FlushHlcHighWater_RoundTrips()
    {
        using var conn = Open();
        LocalDbSchema.EnsureCreated(conn);

        LocalDbSchema.FlushHlcHighWater(conn, 123_456_789L);

        var (_, highWater) = LocalDbSchema.ReadMeta(conn);
        Assert.Equal(123_456_789L, highWater);
    }
}
