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

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

        public AdvancedQueryTests()
        {
            _dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_advanced_{Guid.NewGuid()}.db");
            _db = new Shared.TestDbContext(_dbPath);

            // Seed Data
            _db.TestDocuments.Insert(new TestDocument { Category = "A", Amount = 10, Name = "Item1" });
            _db.TestDocuments.Insert(new TestDocument { Category = "A", Amount = 20, Name = "Item2" });
            _db.TestDocuments.Insert(new TestDocument { Category = "B", Amount = 30, Name = "Item3" });
            _db.TestDocuments.Insert(new TestDocument { Category = "B", Amount = 40, Name = "Item4" });
            _db.TestDocuments.Insert(new TestDocument { Category = "C", Amount = 50, Name = "Item5" });
            _db.SaveChanges();
        }

        public void Dispose()
        {
            _db.Dispose();
            if (File.Exists(_dbPath)) File.Delete(_dbPath);
        }

        [Fact]
        public void GroupBy_Simple_Key_Works()
        {
            var groups = _db.TestDocuments.AsQueryable()
                .GroupBy(x => x.Category)
                .ToList();

            groups.Count.ShouldBe(3);

            var groupA = groups.First(g => g.Key == "A");
            groupA.Count().ShouldBe(2);
            groupA.ShouldContain(x => x.Amount == 10);
            groupA.ShouldContain(x => x.Amount == 20);

            var groupB = groups.First(g => g.Key == "B");
            groupB.Count().ShouldBe(2);

            var groupC = groups.First(g => g.Key == "C");
            groupC.Count().ShouldBe(1);
        }

        [Fact]
        public void GroupBy_With_Aggregation_Select()
        {
            var results = _db.TestDocuments.AsQueryable()
                .GroupBy(x => x.Category)
                .Select(g => new { Category = g.Key, Total = g.Sum(x => x.Amount) })
                .OrderBy(x => x.Category)
                .ToList();

            results.Count.ShouldBe(3);
            results[0].Category.ShouldBe("A");
            results[0].Total.ShouldBe(30); // 10 + 20

            results[1].Category.ShouldBe("B");
            results[1].Total.ShouldBe(70); // 30 + 40

            results[2].Category.ShouldBe("C");
            results[2].Total.ShouldBe(50); // 50
        }

        [Fact]
        public void Aggregations_Direct_Works()
        {
            var query = _db.TestDocuments.AsQueryable();

            query.Count().ShouldBe(5);
            query.Sum(x => x.Amount).ShouldBe(150);
            query.Average(x => x.Amount).ShouldBe(30.0);
            query.Min(x => x.Amount).ShouldBe(10);
            query.Max(x => x.Amount).ShouldBe(50);
        }

        [Fact]
        public void Aggregations_With_Predicate_Works()
        {
            var query = _db.TestDocuments.AsQueryable().Where(x => x.Category == "A");

            query.Count().ShouldBe(2);
            query.Sum(x => x.Amount).ShouldBe(30);
        }

        [Fact]
        public void Join_Works_InMemory()
        {
            // Create a second collection for joining
            _db.OrderDocuments.Insert(new OrderDocument { ItemName = "Item1", Quantity = 5 });
            _db.OrderDocuments.Insert(new OrderDocument { ItemName = "Item3", Quantity = 2 });
            _db.SaveChanges();

            var query = _db.TestDocuments.AsQueryable()
                .Join(_db.OrderDocuments.AsQueryable(),
                    doc => doc.Name,
                    order => order.ItemName,
                    (doc, order) => new { doc.Name, doc.Category, order.Quantity })
                .OrderBy(x => x.Name)
                .ToList();

            query.Count.ShouldBe(2);

            query[0].Name.ShouldBe("Item1");
            query[0].Category.ShouldBe("A");
            query[0].Quantity.ShouldBe(5);

            query[1].Name.ShouldBe("Item3");
            query[1].Category.ShouldBe("B");
            query[1].Quantity.ShouldBe(2);
        }


        [Fact]
        public void Select_Project_Nested_Object()
        {
            var doc = new ComplexDocument
            {
                Id = ObjectId.NewObjectId(),
                Title = "Order1",
                ShippingAddress = new Address { City = new City { Name = "New York" }, Street = "5th Ave" },
                Items = new List<OrderItem>
                {
                    new OrderItem { Name = "Laptop", Price = 1000 },
                    new OrderItem { Name = "Mouse", Price = 50 }
                }
            };
            _db.ComplexDocuments.Insert(doc);
            _db.SaveChanges();

            var query = _db.ComplexDocuments.AsQueryable()
                .Select(x => x.ShippingAddress)
                .ToList();

            query.Count().ShouldBe(1);
            query[0].City.Name.ShouldBe("New York");
            query[0].Street.ShouldBe("5th Ave");
        }

        [Fact]
        public void Select_Project_Nested_Field()
        {
            var doc = new ComplexDocument
            {
                Id = ObjectId.NewObjectId(),
                Title = "Order1",
                ShippingAddress = new Address { City = new City { Name = "New York" }, Street = "5th Ave" }
            };
            _db.ComplexDocuments.Insert(doc);
            _db.SaveChanges();

            var cities = _db.ComplexDocuments.AsQueryable()
                .Select(x => x.ShippingAddress.City.Name)
                .ToList();

            cities.Count().ShouldBe(1);
            cities[0].ShouldBe("New York");
        }

        [Fact]
        public void Select_Anonymous_Complex()
        {
            ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers.ZB_MOM_WW_CBDD_Shared_CityMapper cityMapper = new ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers.ZB_MOM_WW_CBDD_Shared_CityMapper();
            var doc = new ComplexDocument
            {
                Id = ObjectId.NewObjectId(),
                Title = "Order1",
                ShippingAddress = new Address { City = new City { Name = "New York" }, Street = "5th Ave" }
            };


            _db.ComplexDocuments.Insert(doc);
            _db.SaveChanges();

            var result = _db.ComplexDocuments.AsQueryable()
                .Select(x => new { x.Title, x.ShippingAddress.City })
                .ToList();

            result.Count().ShouldBe(1);
            result[0].Title.ShouldBe("Order1");
            result[0].City.Name.ShouldBe("New York");
        }

        [Fact]
        public void Select_Project_Nested_Array_Of_Objects()
        {
            var doc = new ComplexDocument
            {
                Id = ObjectId.NewObjectId(),
                Title = "Order with Items",
                ShippingAddress = new Address { City = new City { Name = "Los Angeles" }, Street = "Hollywood Blvd" },
                Items = new List<OrderItem>
                {
                    new OrderItem { Name = "Laptop", Price = 1500 },
                    new OrderItem { Name = "Mouse", Price = 25 },
                    new OrderItem { Name = "Keyboard", Price = 75 }
                }
            };
            _db.ComplexDocuments.Insert(doc);
            _db.SaveChanges();

            // Retrieve the full document and verify Items array
            var retrieved = _db.ComplexDocuments.FindAll().First();

            retrieved.Title.ShouldBe("Order with Items");
            retrieved.ShippingAddress.City.Name.ShouldBe("Los Angeles");
            retrieved.ShippingAddress.Street.ShouldBe("Hollywood Blvd");

            // Verify array of nested objects
            retrieved.Items.Count.ShouldBe(3);
            retrieved.Items[0].Name.ShouldBe("Laptop");
            retrieved.Items[0].Price.ShouldBe(1500);
            retrieved.Items[1].Name.ShouldBe("Mouse");
            retrieved.Items[1].Price.ShouldBe(25);
            retrieved.Items[2].Name.ShouldBe("Keyboard");
            retrieved.Items[2].Price.ShouldBe(75);
        }
    }
}
