using Xunit;
using ZB.MOM.WW.CBDD.Core.Query;
using ZB.MOM.WW.CBDD.Core.Indexing;
using System.Linq.Expressions;
using System.Collections.Generic;
using System;

namespace ZB.MOM.WW.CBDD.Tests
{
    public class IndexOptimizationTests
    {
        public class TestEntity
        {
            /// <summary>
            /// Gets or sets the id.
            /// </summary>
            public int Id { get; set; }
            /// <summary>
            /// Gets or sets the name.
            /// </summary>
            public string Name { get; set; } = "";
            /// <summary>
            /// Gets or sets the age.
            /// </summary>
            public int Age { get; set; }
        }

        /// <summary>
        /// Tests optimizer identifies equality.
        /// </summary>
        [Fact]
        public void Optimizer_Identifies_Equality()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Age == 30;
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldNotBeNull();
            result.IndexName.ShouldBe("idx_age");
            result.MinValue.ShouldBe(30);
            result.MaxValue.ShouldBe(30);
            result.IsRange.ShouldBeFalse();
        }

        /// <summary>
        /// Tests optimizer identifies range greater than.
        /// </summary>
        [Fact]
        public void Optimizer_Identifies_Range_GreaterThan()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Age > 25;
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldNotBeNull();
            result.IndexName.ShouldBe("idx_age");
            result.MinValue.ShouldBe(25);
            result.MaxValue.ShouldBeNull();
            result.IsRange.ShouldBeTrue();
        }

        /// <summary>
        /// Tests optimizer identifies range less than.
        /// </summary>
        [Fact]
        public void Optimizer_Identifies_Range_LessThan()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Age < 50;
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldNotBeNull();
            result.IndexName.ShouldBe("idx_age");
            result.MinValue.ShouldBeNull();
            result.MaxValue.ShouldBe(50);
            result.IsRange.ShouldBeTrue();
        }

        /// <summary>
        /// Tests optimizer identifies range between simulated.
        /// </summary>
        [Fact]
        public void Optimizer_Identifies_Range_Between_Simulated()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Age > 20 && x.Age < 40;
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldNotBeNull();
            result.IndexName.ShouldBe("idx_age");
            result.MinValue.ShouldBe(20);
            result.MaxValue.ShouldBe(40);
            result.IsRange.ShouldBeTrue();
        }

        /// <summary>
        /// Tests optimizer identifies starts with.
        /// </summary>
        [Fact]
        public void Optimizer_Identifies_StartsWith()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_name", PropertyPaths = ["Name"], Type = IndexType.BTree }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Name.StartsWith("Ali");
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldNotBeNull();
            result.IndexName.ShouldBe("idx_name");
            result.MinValue.ShouldBe("Ali");
            // "Ali" + next char -> "Alj"
            result.MaxValue.ShouldBe("Alj");
            result.IsRange.ShouldBeTrue();
        }

        /// <summary>
        /// Tests optimizer ignores non indexed fields.
        /// </summary>
        [Fact]
        public void Optimizer_Ignores_NonIndexed_Fields()
        {
            var indexes = new List<CollectionIndexInfo>
            {
                new CollectionIndexInfo { Name = "idx_age", PropertyPaths = ["Age"] }
            };

            Expression<Func<TestEntity, bool>> predicate = x => x.Name == "Alice"; // Name is not indexed
            var model = new QueryModel { WhereClause = predicate };

            var result = IndexOptimizer.TryOptimize<TestEntity>(model, indexes);

            result.ShouldBeNull();
        }
    }
}
