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

namespace ZB.MOM.WW.CBDD.Tests;

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

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

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

        SeedData();
    }

    /// <summary>
    ///     Executes Dispose.
    /// </summary>
    public void Dispose()
    {
        _storage.Dispose();
        File.Delete(_testFile);
    }

    private void SeedData()
    {
        // Insert keys: 10, 20, 30, 40, 50
        // And strings: "A", "AB", "ABC", "B", "C"

        ulong txnId = _storage.BeginTransaction().TransactionId;

        Insert(10, txnId);
        Insert(20, txnId);
        Insert(30, txnId);
        Insert(40, txnId);
        Insert(50, txnId);

        Insert("A", txnId);
        Insert("AB", txnId);
        Insert("ABC", txnId);
        Insert("B", txnId);
        Insert("C", txnId);

        _storage.CommitTransaction(txnId);
    }

    private void Insert(dynamic value, ulong txnId)
    {
        IndexKey key;
        if (value is int i) key = IndexKey.Create(i);
        else if (value is string s) key = IndexKey.Create(s);
        else throw new ArgumentException();

        _index.Insert(key, new DocumentLocation(1, 1), txnId);
    }

    /// <summary>
    ///     Executes Equal_ShouldFindExactMatch.
    /// </summary>
    [Fact]
    public void Equal_ShouldFindExactMatch()
    {
        var key = IndexKey.Create(30);
        var result = _index.Equal(key, 0).ToList();

        result.Count().ShouldBe(1);
        result[0].Key.ShouldBe(key);
    }

    /// <summary>
    ///     Executes Equal_ShouldReturnEmpty_WhenNotFound.
    /// </summary>
    [Fact]
    public void Equal_ShouldReturnEmpty_WhenNotFound()
    {
        var key = IndexKey.Create(25);
        var result = _index.Equal(key, 0).ToList();

        result.ShouldBeEmpty();
    }

    /// <summary>
    ///     Executes GreaterThan_ShouldReturnMatches.
    /// </summary>
    [Fact]
    public void GreaterThan_ShouldReturnMatches()
    {
        var key = IndexKey.Create(30);
        var result = _index.GreaterThan(key, false, 0).ToList();

        (result.Count >= 2).ShouldBeTrue();
        result[0].Key.ShouldBe(IndexKey.Create(40));
        result[1].Key.ShouldBe(IndexKey.Create(50));
    }

    /// <summary>
    ///     Executes GreaterThanOrEqual_ShouldReturnMatches.
    /// </summary>
    [Fact]
    public void GreaterThanOrEqual_ShouldReturnMatches()
    {
        var key = IndexKey.Create(30);
        var result = _index.GreaterThan(key, true, 0).ToList();

        (result.Count >= 3).ShouldBeTrue();
        result[0].Key.ShouldBe(IndexKey.Create(30));
        result[1].Key.ShouldBe(IndexKey.Create(40));
        result[2].Key.ShouldBe(IndexKey.Create(50));
    }

    /// <summary>
    ///     Executes LessThan_ShouldReturnMatches.
    /// </summary>
    [Fact]
    public void LessThan_ShouldReturnMatches()
    {
        var key = IndexKey.Create(30);
        var result = _index.LessThan(key, false, 0).ToList();

        result.Count.ShouldBe(2); // 20, 10 (Order is backward?)
        // LessThan yields backward?
        // Implementation: MovePrev(). So yes, 20 then 10.
        result[0].Key.ShouldBe(IndexKey.Create(20));
        result[1].Key.ShouldBe(IndexKey.Create(10));
    }

    /// <summary>
    ///     Executes Between_ShouldReturnRange.
    /// </summary>
    [Fact]
    public void Between_ShouldReturnRange()
    {
        var start = IndexKey.Create(20);
        var end = IndexKey.Create(40);
        var result = _index.Between(start, end, true, true, 0).ToList();

        result.Count.ShouldBe(3); // 20, 30, 40
        result[0].Key.ShouldBe(IndexKey.Create(20));
        result[1].Key.ShouldBe(IndexKey.Create(30));
        result[2].Key.ShouldBe(IndexKey.Create(40));
    }

    /// <summary>
    ///     Executes StartsWith_ShouldReturnPrefixMatches.
    /// </summary>
    [Fact]
    public void StartsWith_ShouldReturnPrefixMatches()
    {
        var result = _index.StartsWith("AB", 0).ToList();

        result.Count.ShouldBe(2); // AB, ABC
        result[0].Key.ShouldBe(IndexKey.Create("AB"));
        result[1].Key.ShouldBe(IndexKey.Create("ABC"));
    }

    /// <summary>
    ///     Executes Like_ShouldSupportWildcards.
    /// </summary>
    [Fact]
    public void Like_ShouldSupportWildcards()
    {
        // "A%" -> A, AB, ABC
        var result = _index.Like("A%", 0).ToList();
        result.Count.ShouldBe(3);

        // "%B%" -> AB, ABC, B
        var result2 = _index.Like("%B%", 0).ToList();
        // A (no), AB (yes), ABC (yes), B (yes), C (no)
        result2.Count.ShouldBe(3); // AB, ABC, B. Wait, order?
        // Index order: A, AB, ABC, B, C.
        // AB ok. ABC ok. B ok.
    }

    /// <summary>
    ///     Executes Like_Underscore_ShouldMatchSingleChar.
    /// </summary>
    [Fact]
    public void Like_Underscore_ShouldMatchSingleChar()
    {
        // "_B" -> AB (yes), B (no: len 1), ABC (no)
        var result = _index.Like("_B", 0).ToList();
        result.Count().ShouldBe(1);
        result[0].Key.ShouldBe(IndexKey.Create("AB"));
    }

    /// <summary>
    ///     Executes In_ShouldReturnSpecificKeys.
    /// </summary>
    [Fact]
    public void In_ShouldReturnSpecificKeys()
    {
        var keys = new[] { IndexKey.Create(10), IndexKey.Create(30), IndexKey.Create(50), IndexKey.Create(99) };
        var result = _index.In(keys, 0).ToList();

        result.Count.ShouldBe(3); // 10, 30, 50. (99 missing)
        result[0].Key.ShouldBe(IndexKey.Create(10));
        result[1].Key.ShouldBe(IndexKey.Create(30));
        result[2].Key.ShouldBe(IndexKey.Create(50));
    }
}