using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Bson;
using Xunit;

namespace ZB.MOM.WW.CBDD.Tests;

public class CursorTests : IDisposable
{
    private readonly string _testFile;
    private readonly StorageEngine _storage;
    private readonly BTreeIndex _index;

    /// <summary>
    /// Initializes a new instance of the <see cref="CursorTests"/> class.
    /// </summary>
    public CursorTests()
    {
        _testFile = Path.Combine(Path.GetTempPath(), $"docdb_cursor_test_{Guid.NewGuid()}.db");
        _storage = new StorageEngine(_testFile, PageFileConfig.Default);

        var options = IndexOptions.CreateBTree("test");
        _index = new BTreeIndex(_storage, options);

        SeedData();
    }

    private void SeedData()
    {
        var txnId = _storage.BeginTransaction().TransactionId;

        // Insert 10, 20, 30
        _index.Insert(IndexKey.Create(10), new DocumentLocation(1, 0), txnId);
        _index.Insert(IndexKey.Create(20), new DocumentLocation(2, 0), txnId);
        _index.Insert(IndexKey.Create(30), new DocumentLocation(3, 0), txnId);

        _storage.CommitTransaction(txnId);
    }

    /// <summary>
    /// Tests move to first should position at first.
    /// </summary>
    [Fact]
    public void MoveToFirst_ShouldPositionAtFirst()
    {
        using var cursor = _index.CreateCursor(0);
        cursor.MoveToFirst().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(10));
    }

    /// <summary>
    /// Tests move to last should position at last.
    /// </summary>
    [Fact]
    public void MoveToLast_ShouldPositionAtLast()
    {
        using var cursor = _index.CreateCursor(0);
        cursor.MoveToLast().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(30));
    }

    /// <summary>
    /// Tests move next should traverse forward.
    /// </summary>
    [Fact]
    public void MoveNext_ShouldTraverseForward()
    {
        using var cursor = _index.CreateCursor(0);
        cursor.MoveToFirst();

        cursor.MoveNext().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(20));

        cursor.MoveNext().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(30));

        cursor.MoveNext().ShouldBeFalse(); // End
    }

    /// <summary>
    /// Tests move prev should traverse backward.
    /// </summary>
    [Fact]
    public void MovePrev_ShouldTraverseBackward()
    {
        using var cursor = _index.CreateCursor(0);
        cursor.MoveToLast();

        cursor.MovePrev().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(20));

        cursor.MovePrev().ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(10));

        cursor.MovePrev().ShouldBeFalse(); // Start
    }

    /// <summary>
    /// Tests seek should position exact or next.
    /// </summary>
    [Fact]
    public void Seek_ShouldPositionExact_OrNext()
    {
        using var cursor = _index.CreateCursor(0);

        // Exact
        cursor.Seek(IndexKey.Create(20)).ShouldBeTrue();
        cursor.Current.Key.ShouldBe(IndexKey.Create(20));

        // Non-exact (15 -> should land on 20)
        cursor.Seek(IndexKey.Create(15)).ShouldBeFalse();
        cursor.Current.Key.ShouldBe(IndexKey.Create(20));

        // Non-exact (35 -> should be invalid/end)
        cursor.Seek(IndexKey.Create(35)).ShouldBeFalse();
        // Current should throw invalid
        Should.Throw<InvalidOperationException>(() => cursor.Current);
    }

    /// <summary>
    /// Disposes the resources used by this instance.
    /// </summary>
    public void Dispose()
    {
        _storage.Dispose();
        if (File.Exists(_testFile)) File.Delete(_testFile);
    }
}
