using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Bson;
using Xunit;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using ZB.MOM.WW.CBDD.Shared;

namespace ZB.MOM.WW.CBDD.Tests
{
    public class LinqTests : IDisposable
    {
        private readonly string _testFile;
        private readonly Shared.TestDbContext _db;

        /// <summary>
        /// Initializes a new instance of the <see cref="LinqTests"/> class.
        /// </summary>
        public LinqTests()
        {
            _testFile = Path.Combine(Path.GetTempPath(), $"linq_tests_{Guid.NewGuid()}.db");
            if (File.Exists(_testFile)) File.Delete(_testFile);
            var wal = Path.ChangeExtension(_testFile, ".wal");
            if (File.Exists(wal)) File.Delete(wal);

            _db = new Shared.TestDbContext(_testFile);

            // Seed Data
            _db.Users.Insert(new User { Name = "Alice", Age = 30 });
            _db.Users.Insert(new User { Name = "Bob", Age = 25 });
            _db.Users.Insert(new User { Name = "Charlie", Age = 35 });
            _db.Users.Insert(new User { Name = "Dave", Age = 20 });
            _db.Users.Insert(new User { Name = "Eve", Age = 40 });
            _db.SaveChanges();
        }

        /// <summary>
        /// Disposes test resources and removes temporary files.
        /// </summary>
        public void Dispose()
        {
            _db.Dispose();
            if (File.Exists(_testFile)) File.Delete(_testFile);
            var wal = Path.ChangeExtension(_testFile, ".wal");
            if (File.Exists(wal)) File.Delete(wal);
        }

        /// <summary>
        /// Verifies where filters return matching documents.
        /// </summary>
        [Fact]
        public void Where_FiltersDocuments()
        {
            var query = _db.Users.AsQueryable().Where(x => x.Age > 28);
            var results = query.ToList();

            results.Count.ShouldBe(3); // Alice(30), Charlie(35), Eve(40)
            results.ShouldNotContain(d => d.Name == "Bob");
        }

        /// <summary>
        /// Verifies order by returns sorted documents.
        /// </summary>
        [Fact]
        public void OrderBy_SortsDocuments()
        {
            var results = _db.Users.AsQueryable().OrderBy(x => x.Age).ToList();

            results.Count.ShouldBe(5);
            results[0].Name.ShouldBe("Dave"); // 20
            results[1].Name.ShouldBe("Bob");  // 25
            results.Last().Name.ShouldBe("Eve"); // 40
        }

        /// <summary>
        /// Verifies skip and take support pagination.
        /// </summary>
        [Fact]
        public void SkipTake_Pagination()
        {
            var results = _db.Users.AsQueryable()
                .OrderBy(x => x.Age)
                .Skip(1)
                .Take(2)
                .ToList();

            results.Count.ShouldBe(2);
            results[0].Name.ShouldBe("Bob"); // 25 (Skipped Dave)
            results[1].Name.ShouldBe("Alice"); // 30
        }

        /// <summary>
        /// Verifies select supports projections.
        /// </summary>
        [Fact]
        public void Select_Projections()
        {
            var names = _db.Users.AsQueryable()
                .Where(x => x.Age < 30)
                .OrderBy(x => x.Age)
                .Select(x => x.Name)
                .ToList();

            names.Count.ShouldBe(2);
            names[0].ShouldBe("Dave");
            names[1].ShouldBe("Bob");
        }
        /// <summary>
        /// Verifies indexed where queries use index-backed filtering.
        /// </summary>
        [Fact]
        public void IndexedWhere_UsedIndex()
        {
            // Create index on Age
            _db.Users.EnsureIndex(x => x.Age, "idx_age", false);

            var query = _db.Users.AsQueryable().Where(x => x.Age > 25);
            var results = query.ToList();

            results.Count.ShouldBe(3); // Alice(30), Charlie(35), Eve(40)
            results.ShouldNotContain(d => d.Name == "Bob"); // Age 25 (filtered out by strict >)
            results.ShouldNotContain(d => d.Name == "Dave"); // Age 20
        }
        /// <summary>
        /// Verifies starts-with predicates can use an index.
        /// </summary>
        [Fact]
        public void StartsWith_UsedIndex()
        {
            // Create index on Name
            _db.Users.EnsureIndex(x => x.Name!, "idx_name", false);

            // StartsWith "Cha" -> Should find "Charlie"
            var query = _db.Users.AsQueryable().Where(x => x.Name!.StartsWith("Cha"));
            var results = query.ToList();

            results.Count().ShouldBe(1);
            results[0].Name.ShouldBe("Charlie");
        }

        /// <summary>
        /// Verifies range predicates can use an index.
        /// </summary>
        [Fact]
        public void Between_UsedIndex()
        {
            // Create index on Age
            _db.Users.EnsureIndex(x => x.Age, "idx_age_between", false);

            // Age >= 22 && Age <= 32
            // Alice(30), Bob(25) -> Should be found.
            // Dave(20), Charlie(35), Eve(40) -> excluded.

            var query = _db.Users.AsQueryable().Where(x => x.Age >= 22 && x.Age <= 32);
            var results = query.ToList();

            results.Count.ShouldBe(2);
            results.ShouldContain(x => x.Name == "Alice");
            results.ShouldContain(x => x.Name == "Bob");
        }
    }
}
