using ZB.MOM.WW.CBDD.Shared;

namespace ZB.MOM.WW.CBDD.Tests;

public class AsyncTests : IDisposable
{
    private readonly string _dbPath;

    /// <summary>
    ///     Initializes a new instance of the <see cref="AsyncTests" /> class.
    /// </summary>
    public AsyncTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_async_{Guid.NewGuid()}.db");
    }

    /// <summary>
    ///     Executes Dispose.
    /// </summary>
    public void Dispose()
    {
        if (File.Exists(_dbPath)) File.Delete(_dbPath);
        if (File.Exists(Path.ChangeExtension(_dbPath, ".wal"))) File.Delete(Path.ChangeExtension(_dbPath, ".wal"));
    }

    /// <summary>
    ///     Executes Async_Transaction_Commit_Should_Persist_Data.
    /// </summary>
    [Fact]
    public async Task Async_Transaction_Commit_Should_Persist_Data()
    {
        var ct = TestContext.Current.CancellationToken;

        using (var db = new TestDbContext(_dbPath))
        {
            using (var txn = await db.BeginTransactionAsync(ct))
            {
                db.AsyncDocs.Insert(new AsyncDoc { Id = 1, Name = "Async1" });
                db.AsyncDocs.Insert(new AsyncDoc { Id = 2, Name = "Async2" });
                await db.SaveChangesAsync(ct);
            }
        }

        // Verify with new storage engine instance
        using var db2 = new TestDbContext(_dbPath);
        var doc1 = db2.AsyncDocs.FindById(1);
        doc1.ShouldNotBeNull();
        doc1.Name.ShouldBe("Async1");

        var doc2 = db2.AsyncDocs.FindById(2);
        doc2.ShouldNotBeNull();
        doc2.Name.ShouldBe("Async2");
    }

    /// <summary>
    ///     Executes Async_Transaction_Rollback_Should_Discard_Data.
    /// </summary>
    [Fact]
    public async Task Async_Transaction_Rollback_Should_Discard_Data()
    {
        var ct = TestContext.Current.CancellationToken;

        using var db = new TestDbContext(_dbPath);
        using (var txn = await db.BeginTransactionAsync(ct))
        {
            db.AsyncDocs.Insert(new AsyncDoc { Id = 3, Name = "RollbackMe" });
        }

        var doc = db.AsyncDocs.FindById(3);
        doc.ShouldBeNull();
    }

    /// <summary>
    ///     Executes Bulk_Async_Insert_Should_Persist_Data.
    /// </summary>
    [Fact]
    public async Task Bulk_Async_Insert_Should_Persist_Data()
    {
        using var db = new TestDbContext(_dbPath);
        var docs = Enumerable.Range(1, 100).Select(i => new AsyncDoc { Id = i + 5000, Name = $"Bulk{i}" });

        var ids = await db.AsyncDocs.InsertBulkAsync(docs);

        ids.Count.ShouldBe(100);

        var doc50 = db.AsyncDocs.FindById(5050);
        doc50.ShouldNotBeNull();
        doc50.Name.ShouldBe("Bulk50");
    }

    /// <summary>
    ///     Executes Bulk_Async_Update_Should_Persist_Changes.
    /// </summary>
    [Fact]
    public async Task Bulk_Async_Update_Should_Persist_Changes()
    {
        using var db = new TestDbContext(_dbPath);
        // 1. Insert 100 docs
        var docs = Enumerable.Range(1, 100).Select(i => new AsyncDoc { Id = i + 6000, Name = $"Original{i}" }).ToList();
        await db.AsyncDocs.InsertBulkAsync(docs);

        // 2. Update all docs
        foreach (var doc in docs) doc.Name = $"Updated{doc.Id - 6000}";

        int count = await db.AsyncDocs.UpdateBulkAsync(docs);

        count.ShouldBe(100);

        // 3. Verify updates
        var doc50 = db.AsyncDocs.FindById(6050);
        doc50.ShouldNotBeNull();
        doc50.Name.ShouldBe("Updated50");
    }

    /// <summary>
    ///     Executes High_Concurrency_Async_Commits.
    /// </summary>
    [Fact]
    public async Task High_Concurrency_Async_Commits()
    {
        var ct = TestContext.Current.CancellationToken;

        using var db =
            new TestDbContext(Path.Combine(Path.GetTempPath(), $"cbdd_async_concurrency_{Guid.NewGuid()}.db"));
        var threadCount = 2;
        var docsPerThread = 50;

        var tasks = Enumerable.Range(0, threadCount).Select(async i =>
        {
            // Test mix of implicit and explicit transactions
            for (var j = 0; j < docsPerThread; j++)
            {
                int id = i * docsPerThread + j + 8000;
                await db.AsyncDocs.InsertAsync(new AsyncDoc { Id = id, Name = $"Thread{i}_Doc{j}" });
            }
        });

        await Task.WhenAll(tasks);
        await db.SaveChangesAsync(ct);

        // Verify count
        int count = db.AsyncDocs.Scan(_ => true).Count();
        count.ShouldBe(threadCount * docsPerThread);
    }
}