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

namespace ZB.MOM.WW.LocalDb.Tests;

public sealed class RegistrationTests : 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 SqliteLocalDb Create() => new(new LocalDbOptions { Path = _path });

    private static async Task CreateOrders(SqliteLocalDb db) =>
        await db.ExecuteAsync("CREATE TABLE orders (id INTEGER PRIMARY KEY, sku TEXT, qty INTEGER)");

    private Task<IReadOnlyList<string>> Triggers(SqliteLocalDb db) =>
        db.QueryAsync(
            "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name='orders' ORDER BY name",
            r => r.GetString(0));

    [Fact]
    public async Task Register_TableWithPk_InstallsThreeTriggers()
    {
        using var db = Create();
        await CreateOrders(db);

        db.RegisterReplicated("orders");

        var triggers = await Triggers(db);
        Assert.Equal(
            new[] { "__localdb_orders_ad", "__localdb_orders_ai", "__localdb_orders_au" },
            triggers);
    }

    [Fact]
    public async Task Register_NoPk_Throws()
    {
        using var db = Create();
        await db.ExecuteAsync("CREATE TABLE nopk (a TEXT, b TEXT)");

        var ex = Assert.Throws<LocalDbRegistrationException>(() => db.RegisterReplicated("nopk"));
        Assert.Contains("nopk", ex.Message);
    }

    [Fact]
    public void Register_MissingTable_Throws()
    {
        using var db = Create();

        var ex = Assert.Throws<LocalDbRegistrationException>(() => db.RegisterReplicated("ghost"));
        Assert.Contains("ghost", ex.Message);
    }

    [Fact]
    public async Task Register_BlobColumn_Throws()
    {
        using var db = Create();
        await db.ExecuteAsync("CREATE TABLE blobby (id INTEGER PRIMARY KEY, data BLOB)");

        var ex = Assert.Throws<LocalDbRegistrationException>(() => db.RegisterReplicated("blobby"));
        Assert.Contains("blobby", ex.Message);
        Assert.Contains("data", ex.Message);
        Assert.Contains("BLOB", ex.Message);
    }

    [Fact]
    public async Task Register_IsIdempotent_RegeneratesTriggers()
    {
        using var db = Create();
        await CreateOrders(db);

        db.RegisterReplicated("orders");
        db.RegisterReplicated("orders");

        var triggers = await Triggers(db);
        Assert.Equal(3, triggers.Count);
    }

    [Fact]
    public async Task Register_CompositePk_Works()
    {
        using var db = Create();
        await db.ExecuteAsync("CREATE TABLE kv (a INTEGER, b INTEGER, v TEXT, PRIMARY KEY (a, b))");

        db.RegisterReplicated("kv");
        await db.ExecuteAsync("INSERT INTO kv (a, b, v) VALUES (1, 2, 'x')");

        var pk = await db.QueryAsync(
            "SELECT pk_json FROM __localdb_oplog WHERE table_name='kv'",
            r => r.GetString(0));
        Assert.Single(pk);
        Assert.Equal("{\"a\":1,\"b\":2}", pk[0]);
    }

    [Fact]
    public async Task Digest_ChangesWhenSchemaChanges()
    {
        using var db = Create();
        await CreateOrders(db);

        db.RegisterReplicated("orders");
        var before = db.ReplicatedTables["orders"].Digest;

        await db.ExecuteAsync("ALTER TABLE orders ADD COLUMN note TEXT");
        db.RegisterReplicated("orders");
        var after = db.ReplicatedTables["orders"].Digest;

        Assert.NotEqual(before, after);
    }

    [Fact]
    public async Task ReplicatedTables_ExposesRegistry()
    {
        using var db = Create();
        await CreateOrders(db);

        db.RegisterReplicated("orders");

        var table = db.ReplicatedTables["orders"];
        Assert.Equal("orders", table.Name);
        Assert.Equal(new[] { "id" }, table.PkColumns);
        Assert.Equal(new[] { "id", "sku", "qty" }, table.Columns.Select(c => c.Name).ToArray());
    }
}
