using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
using ZB.MOM.WW.CBDD.Shared;
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
using System.IO.Compression;
using System.IO.MemoryMappedFiles;
using Xunit;

namespace ZB.MOM.WW.CBDD.Tests;

public class DocumentOverflowTests : IDisposable
{
    private readonly string _dbPath;
    private readonly Shared.TestDbContext _db;

    public DocumentOverflowTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_{Guid.NewGuid()}.db");
        // Use default PageSize (16KB)
        _db = new Shared.TestDbContext(_dbPath);
    }

    public void Dispose()
    {
        _db.Dispose();
        if (File.Exists(_dbPath)) File.Delete(_dbPath);
    }

    [Fact]
    public void Insert_MediumDoc_64KB_ShouldSucceed()
    {
        // 20KB - Fits in 64KB buffer (First attempt)
        // But triggers overflow pages in storage (20KB > 16KB PageSize)
        var largeString = new string('A', 20 * 1024);
        var user = new User
        {
            Id = ObjectId.NewObjectId(),
            Name = largeString,
            Age = 10
        };

        var id = _db.Users.Insert(user);
        _db.SaveChanges();
        var retrieved = _db.Users.FindById(id);

        retrieved.ShouldNotBeNull();
        retrieved.Name.ShouldBe(largeString);
    }

    [Fact]
    public void Insert_LargeDoc_100KB_ShouldSucceed()
    {
        // 100KB - Fails 64KB buffer, Retries with 2MB
        var largeString = new string('B', 100 * 1024);
        var user = new User
        {
            Id = ObjectId.NewObjectId(),
            Name = largeString,
            Age = 20
        };

        var id = _db.Users.Insert(user);
        _db.SaveChanges();
        var retrieved = _db.Users.FindById(id);

        retrieved.ShouldNotBeNull();
        retrieved.Name.ShouldBe(largeString);
    }

    [Fact]
    public void Insert_HugeDoc_3MB_ShouldSucceed()
    {
        // 3MB - Fails 64KB, Fails 2MB, Retries with 16MB
        var largeString = new string('C', 3 * 1024 * 1024);
        var user = new User
        {
            Id = ObjectId.NewObjectId(),
            Name = largeString,
            Age = 30
        };

        var id = _db.Users.Insert(user);
        _db.SaveChanges();
        var retrieved = _db.Users.FindById(id);

        retrieved.ShouldNotBeNull();
        retrieved.Name.Length.ShouldBe(largeString.Length);
        // Checking full string might be slow, length check + substring check is faster
        retrieved.Name.Substring(0, 100).ShouldBe(largeString.Substring(0, 100));
        retrieved.Name.Substring(retrieved.Name.Length - 100).ShouldBe(largeString.Substring(largeString.Length - 100));
    }

    [Fact]
    public void Update_SmallToHuge_ShouldSucceed()
    {
        // Insert Small
        var user = new User { Id = ObjectId.NewObjectId(), Name = "Small", Age = 1 };
        var id = _db.Users.Insert(user);
        _db.SaveChanges();

        // Update to Huge (3MB)
        var hugeString = new string('U', 3 * 1024 * 1024);
        user.Name = hugeString;

        var updated = _db.Users.Update(user);
        _db.SaveChanges();
        updated.ShouldBeTrue();

        var retrieved = _db.Users.FindById(id);
        retrieved.ShouldNotBeNull();
        retrieved.Name.Length.ShouldBe(hugeString.Length);
    }

    [Fact]
    public void InsertBulk_MixedSizes_ShouldSucceed()
    {
        var users = new List<User>
        {
            new User { Id = ObjectId.NewObjectId(), Name = "Small 1", Age = 1 },
            new User { Id = ObjectId.NewObjectId(), Name = new string('M', 100 * 1024), Age = 2 }, // 100KB
            new User { Id = ObjectId.NewObjectId(), Name = "Small 2", Age = 3 },
            new User { Id = ObjectId.NewObjectId(), Name = new string('H', 3 * 1024 * 1024), Age = 4 } // 3MB
        };

        var ids = _db.Users.InsertBulk(users);
        ids.Count.ShouldBe(4);

        foreach (var u in users)
        {
            var r = _db.Users.FindById(u.Id);
            r.ShouldNotBeNull();
            r.Name.Length.ShouldBe(u.Name.Length);
        }
    }

    [Fact]
    public void Insert_HugeDoc_WithCompressionEnabledAndSmallPages_ShouldSucceed()
    {
        var localDbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_compression_{Guid.NewGuid():N}.db");
        var options = new CompressionOptions
        {
            EnableCompression = true,
            MinSizeBytes = 0,
            MinSavingsPercent = 0,
            Codec = CompressionCodec.Brotli,
            Level = CompressionLevel.Fastest
        };

        try
        {
            using var db = new Shared.TestDbContext(localDbPath, TinyPageConfig(), options);
            var huge = new string('Z', 2 * 1024 * 1024);
            var id = db.Users.Insert(new User
            {
                Id = ObjectId.NewObjectId(),
                Name = huge,
                Age = 50
            });
            db.SaveChanges();

            var loaded = db.Users.FindById(id);
            loaded.ShouldNotBeNull();
            loaded.Name.ShouldBe(huge);
            db.GetCompressionStats().CompressedDocumentCount.ShouldBeGreaterThanOrEqualTo(1);
        }
        finally
        {
            CleanupLocalFiles(localDbPath);
        }
    }

    [Fact]
    public void Update_HugeToSmall_WithCompressionEnabled_ShouldSucceed()
    {
        var localDbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_compression_update_{Guid.NewGuid():N}.db");
        var options = new CompressionOptions
        {
            EnableCompression = true,
            MinSizeBytes = 1024,
            MinSavingsPercent = 0,
            Codec = CompressionCodec.Deflate,
            Level = CompressionLevel.Fastest
        };

        try
        {
            using var db = new Shared.TestDbContext(localDbPath, TinyPageConfig(), options);
            var user = new User
            {
                Id = ObjectId.NewObjectId(),
                Name = new string('Q', 256 * 1024),
                Age = 44
            };

            var id = db.Users.Insert(user);
            db.SaveChanges();

            user.Name = "small-after-overflow";
            db.Users.Update(user).ShouldBeTrue();
            db.SaveChanges();

            var loaded = db.Users.FindById(id);
            loaded.ShouldNotBeNull();
            loaded.Name.ShouldBe("small-after-overflow");
        }
        finally
        {
            CleanupLocalFiles(localDbPath);
        }
    }

    private static PageFileConfig TinyPageConfig()
    {
        return new PageFileConfig
        {
            PageSize = 16 * 1024,
            InitialFileSize = 1024 * 1024,
            Access = MemoryMappedFileAccess.ReadWrite
        };
    }

    private static void CleanupLocalFiles(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);
    }
}
