using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Shared;

namespace ZB.MOM.WW.CBDD.Tests;

public class CompactionWalCoordinationTests
{
    /// <summary>
    /// Verifies offline compaction checkpoints and leaves the WAL empty.
    /// </summary>
    [Fact]
    public void OfflineCompact_ShouldCheckpointAndLeaveWalEmpty()
    {
        var dbPath = NewDbPath();
        var markerPath = $"{dbPath}.compact.state";

        try
        {
            using var db = new TestDbContext(dbPath);
            for (var i = 0; i < 80; i++)
            {
                db.Users.Insert(new User { Name = $"wal-compact-{i:D3}", Age = i });
            }

            db.SaveChanges();
            db.Storage.GetWalSize().ShouldBeGreaterThan(0);

            var stats = db.Compact(new CompactionOptions
            {
                EnableTailTruncation = true,
                NormalizeFreeList = true,
                DefragmentSlottedPages = true
            });

            stats.OnlineMode.ShouldBeFalse();
            db.Storage.GetWalSize().ShouldBe(0);
            File.Exists(markerPath).ShouldBeFalse();

            db.Users.Count().ShouldBe(80);
        }
        finally
        {
            CleanupFiles(dbPath);
        }
    }

    /// <summary>
    /// Verifies compaction after WAL recovery preserves durable data.
    /// </summary>
    [Fact]
    public void Compact_AfterWalRecovery_ShouldKeepDataDurable()
    {
        var dbPath = NewDbPath();
        var walPath = Path.ChangeExtension(dbPath, ".wal");
        var expected = new List<(ObjectId Id, string Name)>();

        try
        {
            using (var writer = new TestDbContext(dbPath))
            {
                for (var i = 0; i < 48; i++)
                {
                    var name = $"recoverable-{i:D3}";
                    var id = writer.Users.Insert(new User { Name = name, Age = i % 13 });
                    expected.Add((id, name));
                }

                writer.SaveChanges();
                writer.Storage.GetWalSize().ShouldBeGreaterThan(0);
            }

            new FileInfo(walPath).Length.ShouldBeGreaterThan(0);

            using (var recovered = new TestDbContext(dbPath))
            {
                recovered.Users.Count().ShouldBe(expected.Count);

                foreach (var item in expected)
                {
                    recovered.Users.FindById(item.Id)!.Name.ShouldBe(item.Name);
                }

                recovered.SaveChanges();
                recovered.Compact();
                recovered.Storage.GetWalSize().ShouldBe(0);
            }

            using (var verify = new TestDbContext(dbPath))
            {
                verify.Users.Count().ShouldBe(expected.Count);
                foreach (var item in expected)
                {
                    verify.Users.FindById(item.Id)!.Name.ShouldBe(item.Name);
                }
            }
        }
        finally
        {
            CleanupFiles(dbPath);
        }
    }

    private static string NewDbPath()
        => Path.Combine(Path.GetTempPath(), $"compaction_wal_{Guid.NewGuid():N}.db");

    private static void CleanupFiles(string dbPath)
    {
        var walPath = Path.ChangeExtension(dbPath, ".wal");
        var markerPath = $"{dbPath}.compact.state";
        if (File.Exists(dbPath)) File.Delete(dbPath);
        if (File.Exists(walPath)) File.Delete(walPath);
        if (File.Exists(markerPath)) File.Delete(markerPath);
    }
}
