using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Storage;
using System.Text;
using Xunit;

namespace ZB.MOM.WW.CBDD.Tests;

public class DictionaryPageTests
{
    private const int PageSize = 16384;

    [Fact]
    public void Initialize_ShouldSetupEmptyPage()
    {
        var page = new byte[PageSize];
        DictionaryPage.Initialize(page, 1);

        var header = PageHeader.ReadFrom(page);
        header.PageType.ShouldBe(PageType.Dictionary);
        header.PageId.ShouldBe(1u);

        var count = BitConverter.ToUInt16(page, 32); // CountOffset
        count.ShouldBe((ushort)0);

        var freeSpaceEnd = BitConverter.ToUInt16(page, 34); // FreeSpaceEndOffset
        freeSpaceEnd.ShouldBe((ushort)PageSize);
    }

    [Fact]
    public void Insert_ShouldAddEntryAndSort()
    {
        var page = new byte[PageSize];
        DictionaryPage.Initialize(page, 1);

        // Insert "B"
        bool inserted = DictionaryPage.Insert(page, "B", 20);
        inserted.ShouldBeTrue();

        // Insert "A" (should go before B)
        inserted = DictionaryPage.Insert(page, "A", 10);
        inserted.ShouldBeTrue();

        // Insert "C" (should go after B)
        inserted = DictionaryPage.Insert(page, "C", 30);
        inserted.ShouldBeTrue();

        // Verify Order
        var entries = DictionaryPage.GetAll(page).ToList();
        entries.Count.ShouldBe(3);

        entries[0].Key.ShouldBe("A");
        entries[0].Value.ShouldBe((ushort)10);

        entries[1].Key.ShouldBe("B");
        entries[1].Value.ShouldBe((ushort)20);

        entries[2].Key.ShouldBe("C");
        entries[2].Value.ShouldBe((ushort)30);
    }

    [Fact]
    public void TryFind_ShouldReturnCorrectValue()
    {
        var page = new byte[PageSize];
        DictionaryPage.Initialize(page, 1);

        DictionaryPage.Insert(page, "Key1", 100);
        DictionaryPage.Insert(page, "Key2", 200);
        DictionaryPage.Insert(page, "Key3", 300);

        bool found = DictionaryPage.TryFind(page, Encoding.UTF8.GetBytes("Key2"), out ushort value);
        found.ShouldBeTrue();
        value.ShouldBe((ushort)200);

        found = DictionaryPage.TryFind(page, Encoding.UTF8.GetBytes("Key999"), out value);
        found.ShouldBeFalse();
    }

    [Fact]
    public void Overflow_ShouldReturnFalse_WhenFull()
    {
        var page = new byte[PageSize];
        DictionaryPage.Initialize(page, 1);

        string bigKey = new string('X', 250);

        int count = 0;
        while (true)
        {
            // Use unique keys
            var key = bigKey + count;
            if (!DictionaryPage.Insert(page, key, (ushort)count))
            {
                // Should fail here
                break;
            }
            count++;
            if (count > 1000) throw new ShouldAssertException("Should have filled the page much earlier");
        }

        // Now page is full enough that `bigKey` (250 bytes) shouldn't fit.
        // We can't guarantee a small key won't fit (fragmentation/remaining space), 
        // but a key of the SAME size that triggered the break should definitely fail.
        bool inserted = DictionaryPage.Insert(page, bigKey + "X", 9999);
        inserted.ShouldBeFalse();
    }

    [Fact]
    public void Chaining_ShouldFindKeysInLinkedPages()
    {
        var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_chain_{Guid.NewGuid()}.db");
        using var storage = new StorageEngine(dbPath, PageFileConfig.Default);

        // 1. Create First Page
        var page1Id = storage.AllocatePage();
        var pageBuffer = new byte[storage.PageSize];
        DictionaryPage.Initialize(pageBuffer, page1Id);

        // Fill Page 1
        DictionaryPage.Insert(pageBuffer, "Key1", 100);
        DictionaryPage.Insert(pageBuffer, "KeyA", 200);

        // 2. Create Second Page
        var page2Id = storage.AllocatePage();
        var page2Buffer = new byte[storage.PageSize];
        DictionaryPage.Initialize(page2Buffer, page2Id);

        // Fill Page 2
        DictionaryPage.Insert(page2Buffer, "Key2", 300);
        DictionaryPage.Insert(page2Buffer, "KeyB", 400); // 400

        // 3. Link Page 1 -> Page 2
        var header1 = PageHeader.ReadFrom(pageBuffer);
        header1.NextPageId = page2Id;
        header1.WriteTo(pageBuffer);

        // 4. Write pages to storage
        storage.WritePageImmediate(page1Id, pageBuffer);
        storage.WritePageImmediate(page2Id, page2Buffer);

        // 5. Test Global Find
        // Find in Page 1
        bool found = DictionaryPage.TryFindGlobal(storage, page1Id, "Key1", out ushort val);
        found.ShouldBeTrue();
        val.ShouldBe((ushort)100);

        // Find in Page 2
        found = DictionaryPage.TryFindGlobal(storage, page1Id, "KeyB", out val);
        found.ShouldBeTrue();
        val.ShouldBe((ushort)400);

        // Not Found
        found = DictionaryPage.TryFindGlobal(storage, page1Id, "KeyMissing", out val);
        found.ShouldBeFalse();

        storage.Dispose();
        if (File.Exists(dbPath)) File.Delete(dbPath);
        if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
    }

    [Fact]
    public void FindAllGlobal_ShouldRetrieveAllKeys()
    {
        var dbPath = Path.Combine(Path.GetTempPath(), $"test_dict_findall_{Guid.NewGuid()}.db");
        using var storage = new StorageEngine(dbPath, PageFileConfig.Default);

        // 1. Create Chain of 3 Pages
        var page1Id = storage.AllocatePage();
        var page2Id = storage.AllocatePage();
        var page3Id = storage.AllocatePage();

        var buf = new byte[storage.PageSize];

        // Page 1
        DictionaryPage.Initialize(buf, page1Id);
        DictionaryPage.Insert(buf, "P1_A", 10);
        DictionaryPage.Insert(buf, "P1_B", 11);
        var h1 = PageHeader.ReadFrom(buf);
        h1.NextPageId = page2Id;
        h1.WriteTo(buf);
        storage.WritePageImmediate(page1Id, buf);

        // Page 2
        DictionaryPage.Initialize(buf, page2Id);
        DictionaryPage.Insert(buf, "P2_A", 20);
        var h2 = PageHeader.ReadFrom(buf);
        h2.NextPageId = page3Id;
        h2.WriteTo(buf);
        storage.WritePageImmediate(page2Id, buf);

        // Page 3
        DictionaryPage.Initialize(buf, page3Id);
        DictionaryPage.Insert(buf, "P3_A", 30);
        DictionaryPage.Insert(buf, "P3_B", 31);
        DictionaryPage.Insert(buf, "P3_C", 32);
        storage.WritePageImmediate(page3Id, buf);

        // 2. Execute FindAllGlobal
        var allEntries = DictionaryPage.FindAllGlobal(storage, page1Id).ToList();

        // 3. Verify
        allEntries.Count.ShouldBe(6);
        allEntries.ShouldContain(e => e.Key == "P1_A" && e.Value == 10);
        allEntries.ShouldContain(e => e.Key == "P2_A" && e.Value == 20);
        allEntries.ShouldContain(e => e.Key == "P3_C" && e.Value == 32);

        storage.Dispose();
        if (File.Exists(dbPath)) File.Delete(dbPath);
        if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
    }
}
