using System.Linq.Expressions;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Expressions;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.SignalRs;

namespace AyCode.Services.Server.Tests.SignalRs.SignalRDatasources;

public abstract partial class SignalRDataSourceTestBase<TDataSource, TIList>
{
    #region Context and Filter Tests

    [TestMethod]
    public virtual void ContextIds_CanBeSetAndRetrieved()
    {
        var dataSource = CreateDataSource(_client, _crudTags);

        var contextIds = new object[] { 1, "test", Guid.NewGuid() };
        dataSource.ContextIds = contextIds;

        Assert.AreEqual(3, dataSource.ContextIds?.Length);
    }

    [TestMethod]
    public virtual void CustomFilterCriteria_CanBeSetAndRetrieved()
    {
        var dataSource = CreateDataSource(_client, _crudTags);
        dataSource.CustomFilterCriteria = "search term";

        Assert.AreEqual("search term", dataSource.CustomFilterCriteria);
    }

    #endregion

    #region Expression / AcLinq Filter Tests

    /// <summary>
    /// Creates CRUD tags for Expression-enabled DataSource.
    /// </summary>
    private SignalRCrudTags CreateExpressionCrudTags() => new(
        TestSignalRTags.ExpressionDataSourceGetAll,
        TestSignalRTags.DataSourceGetItem,
        TestSignalRTags.DataSourceAdd,
        TestSignalRTags.DataSourceUpdate,
        TestSignalRTags.DataSourceRemove
    );

    [TestMethod]
    public virtual async Task ExpressionFilter_WithQuantityGreaterThan_ReturnsFilteredItems()
    {
        // Arrange - Create DataSource with Expression-enabled GetAll tag
        var expressionCrudTags = CreateExpressionCrudTags();
        var dataSource = CreateDataSource(_client, expressionCrudTags);

        // Create filter as Expression - serializer automatically converts to AcExpressionNode
        Expression<Func<TestOrderItem_All_True, bool>> filter = item => item.Quantity > 15;

        // Set filter as ContextIds - serializer handles the conversion
        dataSource.ContextIds = [filter];

        // Act
        await dataSource.LoadDataSource();

        // Assert - _dataSourceItems has: Quantity 10, 20, 30. Only 20 and 30 match > 15
        Assert.AreEqual(2, dataSource.Count);
        Assert.IsTrue(dataSource.All(x => x.Quantity > 15));
    }

    [TestMethod]
    public virtual async Task ExpressionFilter_WithUnitPriceGreaterThan_ReturnsFilteredItems()
    {
        // Arrange
        var expressionCrudTags = CreateExpressionCrudTags();
        var dataSource = CreateDataSource(_client, expressionCrudTags);

        // Create filter as Expression - no manual conversion needed
        var minPrice = 150m;
        Expression<Func<TestOrderItem_All_True, bool>> filter = item => item.UnitPrice > minPrice;

        dataSource.ContextIds = [filter];

        // Act
        await dataSource.LoadDataSource();

        // Assert - _dataSourceItems has: UnitPrice 100, 200, 300. Only 200 and 300 match > 150
        Assert.AreEqual(2, dataSource.Count);
        Assert.IsTrue(dataSource.All(x => x.UnitPrice > 150m));
    }

    [TestMethod]
    public virtual async Task ExpressionFilter_WithNoFilter_ReturnsAllItems()
    {
        // Arrange - Use the standard CRUD tags (no Expression filter) 
        // This tests that regular GetAll still works
        var dataSource = CreateDataSource(_client, _crudTags);

        // No ContextIds set - uses standard DataSourceGetAll

        // Act
        await dataSource.LoadDataSource();

        // Assert - Should return all 3 items
        Assert.AreEqual(3, dataSource.Count);
    }

    [TestMethod]
    public virtual async Task ExpressionFilter_WithProductNameContains_ReturnsFilteredItems()
    {
        // Arrange
        var expressionCrudTags = CreateExpressionCrudTags();
        var dataSource = CreateDataSource(_client, expressionCrudTags);

        // Create filter as Expression
        Expression<Func<TestOrderItem_All_True, bool>> filter = item => item.ProductName!.Contains("A");

        dataSource.ContextIds = [filter];

        // Act
        await dataSource.LoadDataSource();

        // Assert - Only "Product A" contains "A"
        Assert.AreEqual(1, dataSource.Count);
        Assert.AreEqual("Product A", dataSource[0].ProductName);
    }

    [TestMethod]
    public virtual async Task ExpressionFilter_WithComplexCondition_ReturnsFilteredItems()
    {
        // Arrange
        var expressionCrudTags = CreateExpressionCrudTags();
        var dataSource = CreateDataSource(_client, expressionCrudTags);

        // Create filter as Expression - complex condition
        Expression<Func<TestOrderItem_All_True, bool>> filter = item => item.Quantity >= 20 && item.UnitPrice < 250m;

        dataSource.ContextIds = [filter];

        // Act
        await dataSource.LoadDataSource();

        // Assert - Only "Product B" (Quantity=20, UnitPrice=200) matches
        Assert.AreEqual(1, dataSource.Count);
        Assert.AreEqual("Product B", dataSource[0].ProductName);
    }

    #endregion

    #region IQueryable Filter Tests

    /// <summary>
    /// Creates CRUD tags for IQueryable-enabled DataSource.
    /// </summary>
    private SignalRCrudTags CreateQueryableCrudTags() => new(
        TestSignalRTags.QueryableDataSourceGetAll,
        TestSignalRTags.DataSourceGetItem,
        TestSignalRTags.DataSourceAdd,
        TestSignalRTags.DataSourceUpdate,
        TestSignalRTags.DataSourceRemove
    );

    [TestMethod]
    public virtual async Task QueryableFilter_WithLambdaFilter_ReturnsFilteredItems()
    {
        // Arrange - Create DataSource with IQueryable-enabled GetAll tag
        var queryableCrudTags = CreateQueryableCrudTags();
        var dataSource = CreateDataSource(_client, queryableCrudTags);

        // Create a lambda filter Expression - this gets wrapped in Where on the server
        Expression<Func<TestOrderItem_All_True, bool>> filter = x => x.Quantity > 15;
        
        // Convert to AcExpressionNode for sending
        var filterNode = AcExpressionConverter.ToNode(filter);
        dataSource.ContextIds = [filterNode];

        // Act
        await dataSource.LoadDataSource();

        // Assert - _dataSourceItems has: Quantity 10, 20, 30. Only 20 and 30 match > 15
        Assert.AreEqual(2, dataSource.Count);
        Assert.IsTrue(dataSource.All(x => x.Quantity > 15));
    }

    [TestMethod]
    public virtual async Task QueryableFilter_WithDecimalComparison_ReturnsFilteredItems()
    {
        // Arrange
        var queryableCrudTags = CreateQueryableCrudTags();
        var dataSource = CreateDataSource(_client, queryableCrudTags);

        // Create a lambda filter with decimal comparison
        var minPrice = 150m;
        Expression<Func<TestOrderItem_All_True, bool>> filter = x => x.UnitPrice > minPrice;
        
        var filterNode = AcExpressionConverter.ToNode(filter);
        dataSource.ContextIds = [filterNode];

        // Act
        await dataSource.LoadDataSource();

        // Assert - UnitPrice 100, 200, 300. Only 200 and 300 match > 150
        Assert.AreEqual(2, dataSource.Count);
        Assert.IsTrue(dataSource.All(x => x.UnitPrice > 150m));
    }

    [TestMethod]
    public virtual async Task QueryableFilter_WithComplexCondition_ReturnsFilteredItems()
    {
        // Arrange
        var queryableCrudTags = CreateQueryableCrudTags();
        var dataSource = CreateDataSource(_client, queryableCrudTags);

        // Create a filter with AND condition
        Expression<Func<TestOrderItem_All_True, bool>> filter = x => x.Quantity >= 20 && x.UnitPrice < 250m;
        
        var filterNode = AcExpressionConverter.ToNode(filter);
        dataSource.ContextIds = [filterNode];

        // Act
        await dataSource.LoadDataSource();

        // Assert - Only "Product B" (Quantity=20, UnitPrice=200) matches
        Assert.AreEqual(1, dataSource.Count);
        Assert.AreEqual("Product B", dataSource[0].ProductName);
    }

    [TestMethod]
    public virtual async Task QueryableFilter_WithWhereAndCount_ReturnsCount()
    {
        // Arrange - Create DataSource with IQueryable-enabled GetAll tag
        var queryableCrudTags = CreateQueryableCrudTags();
        var dataSource = CreateDataSource(_client, queryableCrudTags);

        // Create a query chain: Where(x => x.Id > 1).Count()
        // This tests the method call chain serialization
        var query = new List<TestOrderItem_All_True>().AsQueryable()
            .Where(x => x.Id > 1);
        
        var queryNode = AcExpressionConverter.ToNode(query.Expression);
        dataSource.ContextIds = [queryNode];

        // Act
        await dataSource.LoadDataSource();

        // Assert - Items with Id > 1 are: Id=2 (Product B), Id=3 (Product C)
        Assert.AreEqual(2, dataSource.Count);
        Assert.IsTrue(dataSource.All(x => x.Id > 1));
    }

    [TestMethod]
    public virtual async Task QueryableFilter_WithNoFilter_ReturnsAllItems()
    {
        // Arrange
        var queryableCrudTags = CreateQueryableCrudTags();
        var dataSource = CreateDataSource(_client, queryableCrudTags);

        // No ContextIds - no filter applied

        // Act
        await dataSource.LoadDataSource();

        // Assert - Should return all 3 items
        Assert.AreEqual(3, dataSource.Count);
    }

    #endregion

    #region IQueryable Aggregate Tests (Count, Sum, etc. executed on server)

    [TestMethod]
    public void QueryableAggregate_CountWithWhere_ExecutesOnServer()
    {
        // Arrange - Server data
        var serverItems = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Product A", Quantity = 10 },
            new() { Id = 2, ProductName = "Product B", Quantity = 20 },
            new() { Id = 3, ProductName = "Product C", Quantity = 30 }
        };
        
        // Build query: .Where(x => x.Id > 1).Count()
        // The Count() is an aggregate that should execute on the server!
        var clientQuery = new List<TestOrderItem_All_True>().AsQueryable()
            .Where(x => x.Id > 1);
        
        // Build the Count() call expression manually
        var countExpression = System.Linq.Expressions.Expression.Call(
            typeof(Queryable),
            nameof(Queryable.Count),
            [typeof(TestOrderItem_All_True)],
            clientQuery.Expression
        );
        
        var queryNode = AcExpressionConverter.ToNode(countExpression);
        
        // Execute on server - returns just the count, NOT the full list!
        var serverQuery = serverItems.AsQueryable();
        var result = AcSerializerCommon.ExecuteQueryFromNode(serverQuery, queryNode);
        
        // Assert - Count should be 2 (Id=2 and Id=3)
        Assert.AreEqual(2, result);
    }

    [TestMethod]
    public void QueryableAggregate_CountWithPredicate_ExecutesOnServer()
    {
        // Arrange - Server data
        var serverItems = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Product A", Quantity = 10 },
            new() { Id = 2, ProductName = "Product B", Quantity = 20 },
            new() { Id = 3, ProductName = "Product C", Quantity = 30 }
        };
        
        // Build query: .Count(x => x.Quantity > 15)
        var clientQuery = new List<TestOrderItem_All_True>().AsQueryable();
        Expression<Func<TestOrderItem_All_True, bool>> predicate = x => x.Quantity > 15;
        
        var countExpression = System.Linq.Expressions.Expression.Call(
            typeof(Queryable),
            nameof(Queryable.Count),
            [typeof(TestOrderItem_All_True)],
            clientQuery.Expression,
            System.Linq.Expressions.Expression.Quote(predicate)
        );
        
        var queryNode = AcExpressionConverter.ToNode(countExpression);
        
        // Execute on server
        var serverQuery = serverItems.AsQueryable();
        var result = AcSerializerCommon.ExecuteQueryFromNode(serverQuery, queryNode);
        
        // Assert - Count should be 2 (Quantity 20 and 30 are > 15)
        Assert.AreEqual(2, result);
    }

    [TestMethod]
    public void QueryableAggregate_Any_ExecutesOnServer()
    {
        // Arrange - Server data
        var serverItems = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Product A", Quantity = 10 },
            new() { Id = 2, ProductName = "Product B", Quantity = 20 },
            new() { Id = 3, ProductName = "Product C", Quantity = 30 }
        };
        
        // Build query: .Any(x => x.Quantity > 25)
        var clientQuery = new List<TestOrderItem_All_True>().AsQueryable();
        Expression<Func<TestOrderItem_All_True, bool>> predicate = x => x.Quantity > 25;
        
        var anyExpression = System.Linq.Expressions.Expression.Call(
            typeof(Queryable),
            nameof(Queryable.Any),
            [typeof(TestOrderItem_All_True)],
            clientQuery.Expression,
            System.Linq.Expressions.Expression.Quote(predicate)
        );
        
        var queryNode = AcExpressionConverter.ToNode(anyExpression);
        
        // Execute on server
        var serverQuery = serverItems.AsQueryable();
        var result = AcSerializerCommon.ExecuteQueryFromNode(serverQuery, queryNode);
        
        // Assert - Any should be true (Quantity 30 is > 25)
        Assert.AreEqual(true, result);
    }

    [TestMethod]
    public void QueryableAggregate_Sum_ExecutesOnServer()
    {
        // Arrange - Server data
        var serverItems = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Product A", Quantity = 10, UnitPrice = 100m },
            new() { Id = 2, ProductName = "Product B", Quantity = 20, UnitPrice = 200m },
            new() { Id = 3, ProductName = "Product C", Quantity = 30, UnitPrice = 300m }
        };
        
        // Build query: .Sum(x => x.Quantity)
        var clientQuery = new List<TestOrderItem_All_True>().AsQueryable();
        Expression<Func<TestOrderItem_All_True, int>> selector = x => x.Quantity;
        
        var sumExpression = System.Linq.Expressions.Expression.Call(
            typeof(Queryable),
            nameof(Queryable.Sum),
            [typeof(TestOrderItem_All_True)],
            clientQuery.Expression,
            System.Linq.Expressions.Expression.Quote(selector)
        );
        
        var queryNode = AcExpressionConverter.ToNode(sumExpression);
        
        // Execute on server
        var serverQuery = serverItems.AsQueryable();
        var result = AcSerializerCommon.ExecuteQueryFromNode(serverQuery, queryNode);
        
        // Assert - Sum should be 60 (10 + 20 + 30)
        Assert.AreEqual(60, result);
    }

    #endregion

    #region IQueryable Diagnostics Tests

    [TestMethod]
    public void QueryableExpression_WhenSerialized_HasCorrectStructure()
    {
        // Arrange - Create an IQueryable with Where
        var query = new List<TestOrderItem_All_True>().AsQueryable().Where(x => x.Id > 1);
        
        // Act - Serialize the expression
        var queryNode = AcExpressionConverter.ToNode(query.Expression);
        
        // Assert - Check structure
        Console.WriteLine($"NodeType: {queryNode.NodeType}");
        Console.WriteLine($"MethodName: {queryNode.MethodName}");
        Console.WriteLine($"DeclaringType: {queryNode.DeclaringType}");
        Console.WriteLine($"GenericArguments: {string.Join(", ", queryNode.GenericArguments ?? [])}");
        Console.WriteLine($"Arguments count: {queryNode.Arguments?.Count}");
        
        Assert.AreEqual(System.Linq.Expressions.ExpressionType.Call, queryNode.NodeType);
        Assert.AreEqual("Where", queryNode.MethodName);
        Assert.AreEqual(2, queryNode.Arguments?.Count);
        
        // First argument should be the source (Constant with null value for IQueryable)
        var firstArg = queryNode.Arguments![0];
        Console.WriteLine($"First arg NodeType: {firstArg.NodeType}");
        Console.WriteLine($"First arg ValueType: {firstArg.ValueType}");
        Assert.AreEqual(System.Linq.Expressions.ExpressionType.Constant, firstArg.NodeType);
        
        // Second argument should be the Quote
        var secondArg = queryNode.Arguments![1];
        Console.WriteLine($"Second arg NodeType: {secondArg.NodeType}");
        Assert.AreEqual(System.Linq.Expressions.ExpressionType.Quote, secondArg.NodeType);
        
        // Quote's Operand should be the Lambda
        Console.WriteLine($"Quote Operand is null: {secondArg.Operand == null}");
        Assert.IsNotNull(secondArg.Operand, "Quote should have Operand");
        Console.WriteLine($"Quote Operand NodeType: {secondArg.Operand!.NodeType}");
        Assert.AreEqual(System.Linq.Expressions.ExpressionType.Lambda, secondArg.Operand.NodeType);
    }

    [TestMethod]
    public void QueryableExpression_ApplyToServer_Works()
    {
        // Arrange - Client query
        var clientQuery = new List<TestOrderItem_All_True>().AsQueryable().Where(x => x.Id > 1);
        var queryNode = AcExpressionConverter.ToNode(clientQuery.Expression);
        
        // Server data
        var serverItems = new List<TestOrderItem_All_True>
        {
            new() { Id = 1, ProductName = "Product A" },
            new() { Id = 2, ProductName = "Product B" },
            new() { Id = 3, ProductName = "Product C" }
        };
        var serverQuery = serverItems.AsQueryable();
        
        // Act - Apply the query expression to server data
        var result = AcSerializerCommon.ApplyQueryFromNode(serverQuery, queryNode);
        var resultList = result.ToList();
        
        // Assert
        Assert.AreEqual(2, resultList.Count);
        Assert.IsTrue(resultList.All(x => x.Id > 1));
    }

    #endregion
}
