using System.IO.Compression;
using System.Text;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Shared;

namespace ZB.MOM.WW.CBDD.Tests;

public class MaintenanceDiagnosticsAndMigrationTests
{
    /// <summary>
    ///     Verifies diagnostics APIs return page usage, compression, and fragmentation data.
    /// </summary>
    [Fact]
    public void DiagnosticsApis_ShouldReturnPageUsageCompressionAndFragmentationData()
    {
        string dbPath = NewDbPath();

        try
        {
            var options = new CompressionOptions
            {
                EnableCompression = true,
                MinSizeBytes = 0,
                MinSavingsPercent = 0,
                Codec = CompressionCodec.Brotli,
                Level = CompressionLevel.Fastest
            };

            using var db = new TestDbContext(dbPath, options);
            for (var i = 0; i < 40; i++)
                db.Users.Insert(new User
                {
                    Name = BuildPayload(i, 9000),
                    Age = i
                });

            db.SaveChanges();
            db.ForceCheckpoint();

            var byType = db.GetPageUsageByPageType();
            byType.Count.ShouldBeGreaterThan(0);
            byType.Any(x => x.PageType == PageType.Data && x.PageCount > 0).ShouldBeTrue();

            var byCollection = db.GetPageUsageByCollection();
            byCollection.Any(x => x.CollectionName.Equals("users", StringComparison.OrdinalIgnoreCase)).ShouldBeTrue();

            var compressionByCollection = db.GetCompressionRatioByCollection();
            var usersCompression = compressionByCollection.First(x =>
                x.CollectionName.Equals("users", StringComparison.OrdinalIgnoreCase));
            usersCompression.DocumentCount.ShouldBeGreaterThan(0);
            usersCompression.BytesBeforeCompression.ShouldBeGreaterThan(0);
            usersCompression.BytesAfterCompression.ShouldBeGreaterThan(0);

            var freeList = db.GetFreeListSummary();
            freeList.PageCount.ShouldBeGreaterThan(0u);

            var fragmentation = db.GetFragmentationMap();
            fragmentation.Pages.Count.ShouldBeGreaterThan(0);
        }
        finally
        {
            CleanupFiles(dbPath);
        }
    }

    /// <summary>
    ///     Verifies compression migration dry-run and apply modes return deterministic stats and preserve data.
    /// </summary>
    [Fact]
    public void MigrateCompression_DryRunAndApply_ShouldReturnDeterministicStatsAndPreserveData()
    {
        string dbPath = NewDbPath();

        try
        {
            using var db = new TestDbContext(dbPath, CompressionOptions.Default);
            var ids = new List<ObjectId>();

            for (var i = 0; i < 60; i++)
                ids.Add(db.Users.Insert(new User
                {
                    Name = BuildPayload(i, 12000),
                    Age = i % 17
                }));

            db.SaveChanges();
            db.ForceCheckpoint();

            var dryRun = db.MigrateCompression(new CompressionMigrationOptions
            {
                DryRun = true,
                Codec = CompressionCodec.Deflate,
                Level = CompressionLevel.Fastest,
                MinSizeBytes = 0,
                MinSavingsPercent = 0,
                IncludeCollections = ["users"]
            });

            dryRun.DryRun.ShouldBeTrue();
            dryRun.DocumentsScanned.ShouldBeGreaterThan(0);
            dryRun.BytesBefore.ShouldBeGreaterThan(0);
            dryRun.BytesEstimatedAfter.ShouldBeGreaterThan(0);

            var apply = db.MigrateCompression(new CompressionMigrationOptions
            {
                DryRun = false,
                Codec = CompressionCodec.Deflate,
                Level = CompressionLevel.Fastest,
                MinSizeBytes = 0,
                MinSavingsPercent = 0,
                IncludeCollections = ["users"]
            });

            apply.DryRun.ShouldBeFalse();
            apply.DocumentsScanned.ShouldBeGreaterThan(0);

            foreach (var id in ids)
            {
                var user = db.Users.FindById(id);
                user.ShouldNotBeNull();
                user!.Name.Length.ShouldBeGreaterThan(1000);
            }
        }
        finally
        {
            CleanupFiles(dbPath);
        }
    }

    private static string BuildPayload(int seed, int approxLength)
    {
        var builder = new StringBuilder(approxLength + 128);
        var i = 0;
        while (builder.Length < approxLength)
        {
            builder.Append("diag-migrate-");
            builder.Append(seed.ToString("D4"));
            builder.Append('-');
            builder.Append(i.ToString("D6"));
            builder.Append('|');
            i++;
        }

        return builder.ToString();
    }

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

    private static void CleanupFiles(string dbPath)
    {
        string walPath = Path.ChangeExtension(dbPath, ".wal");
        var markerPath = $"{dbPath}.compact.state";
        var tempPath = $"{dbPath}.compact.tmp";
        var backupPath = $"{dbPath}.compact.bak";

        if (File.Exists(dbPath)) File.Delete(dbPath);
        if (File.Exists(walPath)) File.Delete(walPath);
        if (File.Exists(markerPath)) File.Delete(markerPath);
        if (File.Exists(tempPath)) File.Delete(tempPath);
        if (File.Exists(backupPath)) File.Delete(backupPath);
    }
}