using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Expressions;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.SignalRs;
using static AyCode.Core.Tests.TestModels.AcSerializerModels;
using AyCode.Core.Tests.Serialization;

namespace AyCode.Services.Server.Tests.SignalRs;

/// <summary>
/// Test service with SignalR-attributed methods for testing ProcessOnReceiveMessage.
/// Uses shared DTOs from AyCode.Core.Tests.TestModels.
/// </summary>
public class TestSignalRService2
{
    #region Primitive Parameter Handlers

    [SignalR(TestSignalRTags.SingleIntParam)]
    public string HandleSingleInt(int value)
    {
        return $"{value}";
    }

    [SignalR(TestSignalRTags.TwoIntParams)]
    public int HandleTwoInts(int a, int b)
    {
        return a + b;
    }

    [SignalR(TestSignalRTags.BoolParam)]
    public bool HandleBool(bool loadRelations)
    {
        return loadRelations;
    }

    [SignalR(TestSignalRTags.StringParam)]
    public string HandleString(string text)
    {
        return $"Echo: {text}";
    }

    [SignalR(TestSignalRTags.GuidParam)]
    public Guid HandleGuid(Guid id)
    {
        return id;
    }

    [SignalR(TestSignalRTags.EnumParam)]
    public TestStatus HandleEnum(TestStatus status)
    {
        return status;
    }

    [SignalR(TestSignalRTags.NoParams)]
    public string HandleNoParams()
    {
        return "OK";
    }

    [SignalR(TestSignalRTags.MultipleTypesParams)]
    public string HandleMultipleTypes(bool flag, string text, int number)
    {
        return $"{flag}-{text}-{number}";
    }

    [SignalR(TestSignalRTags.ThrowsException)]
    public void HandleThrowsException()
    {
        throw new InvalidOperationException("Test exception");
    }

    [SignalR(TestSignalRTags.DecimalParam)]
    public decimal HandleDecimal(decimal value)
    {
        return value * 2;
    }

    [SignalR(TestSignalRTags.DateTimeParam)]
    public DateTime HandleDateTime(DateTime dateTime)
    {
        return dateTime;
    }

    [SignalR(TestSignalRTags.DoubleParam)]
    public double HandleDouble(double value)
    {
        return value;
    }

    [SignalR(TestSignalRTags.LongParam)]
    public long HandleLong(long value)
    {
        return value;
    }

    #endregion

    #region Complex Object Handlers (using shared DTOs)

    [SignalR(TestSignalRTags.TestOrderItemParam)]
    public TestOrderItem_All_True HandleTestOrderItem(TestOrderItem_All_True item)
    {
        return new TestOrderItem_All_True 
        { 
            Id = item.Id, 
            ProductName = $"Processed: {item.ProductName}", 
            Quantity = item.Quantity * 2,
            UnitPrice = item.UnitPrice * 2,
         };
    }

    [SignalR(TestSignalRTags.TestOrderParam)]
    public TestOrder_All_True HandleTestOrder(TestOrder_All_True order)
    {
        return order;
    }

    [SignalR(TestSignalRTags.SharedTagParam)]
    public SharedTag_All_True HandleSharedTag(SharedTag_All_True tag)
    {
        return tag;
    }

    #endregion

    #region Collection Parameter Handlers

    [SignalR(TestSignalRTags.IntArrayParam)]
    public int[] HandleIntArray(int[] values)
    {
        return values.Select(x => x * 2).ToArray();
    }

    [SignalR(TestSignalRTags.GuidArrayParam)]
    public Guid[] HandleGuidArray(Guid[] ids)
    {
        return ids;
    }

    [SignalR(TestSignalRTags.StringListParam)]
    public List<string> HandleStringList(List<string> items)
    {
        return items.Select(x => x.ToUpper()).ToList();
    }

    [SignalR(TestSignalRTags.TestOrderItemListParam)]
    public List<TestOrderItem_All_True> HandleTestOrderItemList(List<TestOrderItem_All_True> items)
    {
        return items;
    }

    [SignalR(TestSignalRTags.IntListParam)]
    public List<int> HandleIntList(List<int> numbers)
    {
        return numbers.Select(x => x * 2).ToList();
    }

    [SignalR(TestSignalRTags.BoolArrayParam)]
    public bool[] HandleBoolArray(bool[] flags)
    {
        return flags;
    }

    [SignalR(TestSignalRTags.MixedWithArrayParam)]
    public string HandleMixedWithArray(bool flag, int[] numbers, string text)
    {
        return $"{flag}-[{string.Join(",", numbers)}]-{text}";
    }

    [SignalR(TestSignalRTags.NestedListParam)]
    public List<List<int>> HandleNestedList(List<List<int>> nestedList)
    {
        return nestedList;
    }

    #endregion

    #region Extended Array Parameter Handlers

    [SignalR(TestSignalRTags.LongArrayParam)]
    public long[] HandleLongArray(long[] values)
    {
        return values;
    }

    [SignalR(TestSignalRTags.DecimalArrayParam)]
    public decimal[] HandleDecimalArray(decimal[] values)
    {
        return values;
    }

    [SignalR(TestSignalRTags.DateTimeArrayParam)]
    public DateTime[] HandleDateTimeArray(DateTime[] values)
    {
        return values;
    }

    [SignalR(TestSignalRTags.EnumArrayParam)]
    public TestStatus[] HandleEnumArray(TestStatus[] values)
    {
        return values;
    }

    [SignalR(TestSignalRTags.DoubleArrayParam)]
    public double[] HandleDoubleArray(double[] values)
    {
        return values;
    }

    [SignalR(TestSignalRTags.SharedTagArrayParam)]
    public SharedTag_All_True[] HandleSharedTagArray(SharedTag_All_True[] tags)
    {
        return tags;
    }

    [SignalR(TestSignalRTags.DictionaryParam)]
    public Dictionary<string, int> HandleDictionary(Dictionary<string, int> dict)
    {
        return dict;
    }

    [SignalR(TestSignalRTags.ObjectArrayParam)]
    public object[] HandleObjectArray(object[] values)
    {
        return values;
    }

    #endregion

    #region Mixed Parameter Handlers

    [SignalR(TestSignalRTags.IntAndDtoParam)]
    public string HandleIntAndDto(int id, TestOrderItem_All_True item)
    {
        return $"{id}-{item?.ProductName}";
    }

    [SignalR(TestSignalRTags.DtoAndListParam)]
    public string HandleDtoAndList(TestOrderItem_All_True item, List<int> numbers)
    {
        return $"{item?.ProductName}-[{string.Join(",", numbers ?? [])}]";
    }

    [SignalR(TestSignalRTags.ThreeComplexParams)]
    public string HandleThreeComplexParams(TestOrderItem_All_True item, List<string> tags, SharedTag_All_True sharedTag)
    {
        return $"{item?.ProductName}-{tags?.Count}-{sharedTag?.Name}";
    }

    [SignalR(TestSignalRTags.FiveParams)]
    public Task<string> HandleFiveParams(int a, string b, bool c, Guid d, decimal e)
    {
        return Task.FromResult($"{a}-{b}-{c}-{d}-{e.ToString(CultureInfo.InvariantCulture)}");
    }

    #endregion

    #region Async Task<T> Method Tests

    [SignalR(TestSignalRTags.AsyncTestOrderItemParam)]
    public async Task<TestOrderItem_All_True> HandleAsyncTestOrderItem(TestOrderItem_All_True item)
    {
        await Task.Delay(1); // Simulate async work
        return new TestOrderItem_All_True 
        { 
            Id = item.Id, 
            ProductName = $"Async: {item.ProductName}", 
            Quantity = item.Quantity * 3,
            UnitPrice = item.UnitPrice * 3,
        };
    }

    [SignalR(TestSignalRTags.AsyncStringParam)]
    public async Task<string> HandleAsyncString(string input)
    {
        await Task.Delay(1);
        return $"Async: {input}";
    }

    [SignalR(TestSignalRTags.AsyncNoParams)]
    public async Task<string> HandleAsyncNoParams()
    {
        await Task.Delay(1);
        return "AsyncOK";
    }

    [SignalR(TestSignalRTags.AsyncIntParam)]
    public async Task<int> HandleAsyncInt(int value)
    {
        await Task.Delay(1);
        return value * 2;
    }

    #endregion

    #region Task.FromResult Tests - Critical for testing non-async methods returning Task
    // PRODUCTION BUG FIX: These methods test the scenario where a method returns Task<T>
    // using Task.FromResult() instead of async/await. Such methods do NOT have 
    // AsyncStateMachineAttribute, so the old InvokeMethod implementation would serialize
    // the Task wrapper instead of awaiting and returning the actual result.

    /// <summary>
    /// Returns Task without async keyword - uses Task.FromResult().
    /// This pattern does NOT have AsyncStateMachineAttribute!
    /// </summary>
    [SignalR(TestSignalRTags.TaskFromResultStringParam)]
    public Task<string> HandleTaskFromResultString(string input)
    {
        return Task.FromResult($"FromResult: {input}");
    }

    /// <summary>
    /// Returns Task&lt;TestOrderItem_All_True&gt; without async keyword.
    /// CRITICAL: This simulates the exact production bug scenario.
    /// </summary>
    [SignalR(TestSignalRTags.TaskFromResultTestOrderItemParam)]
    public Task<TestOrderItem_All_True> HandleTaskFromResultTestOrderItem(TestOrderItem_All_True item)
    {
        return Task.FromResult(new TestOrderItem_All_True
        {
            Id = item.Id,
            ProductName = $"FromResult: {item.ProductName}",
            Quantity = item.Quantity * 2,
            UnitPrice = item.UnitPrice * 2,
        });
    }

    /// <summary>
    /// Returns Task&lt;int&gt; without async keyword.
    /// </summary>
    [SignalR(TestSignalRTags.TaskFromResultIntParam)]
    public Task<int> HandleTaskFromResultInt(int value)
    {
        return Task.FromResult(value * 2);
    }

    /// <summary>
    /// Returns non-generic Task using Task.CompletedTask.
    /// </summary>
    [SignalR(TestSignalRTags.TaskFromResultNoParams)]
    public Task HandleTaskFromResultNoParams()
    {
        return Task.CompletedTask;
    }

    #endregion

    #region Binary Serialization with GenericAttributes Test

    /// <summary>
    /// Tests Binary serialization with GenericAttributes containing string-stored DateTime values.
    /// This reproduces the production bug scenario where DateTime values stored as strings
    /// in GenericAttributes were incorrectly blamed for Binary serialization issues.
    /// </summary>
    [SignalR(TestSignalRTags.GenericAttributesParam)]
    public TestDtoWithGenericAttributes HandleGenericAttributes(TestDtoWithGenericAttributes dto)
    {
        // Return the same DTO to verify Binary round-trip preserves all values
        return dto;
    }

    /// <summary>
    /// Tests Binary serialization with a list of DTOs containing GenericAttributes.
    /// This simulates the production scenario with large datasets (e.g., 1834 orders).
    /// </summary>
    [SignalR(TestSignalRTags.GenericAttributesListParam)]
    public List<TestDtoWithGenericAttributes> HandleGenericAttributesList(List<TestDtoWithGenericAttributes> dtos)
    {
        return dtos;
    }

    #endregion

    #region Large Dataset / List Tests

    /// <summary>
    /// Tests Binary serialization with a list of TestOrder_All_True objects.
    /// Used for testing string interning with deeply nested objects.
    /// </summary>
    [SignalR(TestSignalRTags.TestOrderListParam)]
    public List<TestOrder_All_True> HandleTestOrderList(List<TestOrder_All_True> orders)
    {
        return orders;
    }

    #endregion

    #region Property Mismatch Tests (Server has more properties than Client)
    // Tests for SkipValue string interning bug fix.
    // In these tests, the server sends DTOs with more properties than the client knows about.
    // The client's deserializer must skip unknown properties while maintaining string intern table consistency.

    /// <summary>
    /// Handles server DTO and returns the same DTO.
    /// Client will deserialize this into ClientCustomerDto (fewer properties).
    /// </summary>
    [SignalR(TestSignalRTags.PropertyMismatchParam)]
    public ServerCustomerDto HandlePropertyMismatch(ServerCustomerDto dto)
    {
        return dto;
    }

    /// <summary>
    /// Handles list of server DTOs.
    /// Client will deserialize into List&lt;ClientCustomerDto&gt;.
    /// </summary>
    [SignalR(TestSignalRTags.PropertyMismatchListParam)]
    public List<ServerCustomerDto> HandlePropertyMismatchList(List<ServerCustomerDto> dtos)
    {
        return dtos;
    }

    /// <summary>
    /// Handles server order with nested customer objects.
    /// Client will deserialize into ClientOrderSimple (no nested objects).
    /// </summary>
    [SignalR(TestSignalRTags.PropertyMismatchNestedParam)]
    public ServerOrderWithExtras HandlePropertyMismatchNested(ServerOrderWithExtras order)
    {
        return order;
    }

    /// <summary>
    /// Handles list of server orders with nested customer objects.
    /// Client will deserialize into List&lt;ClientOrderSimple&gt;.
    /// </summary>
    [SignalR(TestSignalRTags.PropertyMismatchNestedListParam)]
    public List<ServerOrderWithExtras> HandlePropertyMismatchNestedList(List<ServerOrderWithExtras> orders)
    {
        return orders;
    }

    #endregion

    #region DataSource CRUD Tests

    private readonly List<TestOrderItem_All_True> _dataSourceItems =
    [
        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 }
    ];

    [SignalR(TestSignalRTags.DataSourceGetAll)]
    public List<TestOrderItem_All_True> DataSourceGetAll() => _dataSourceItems.ToList();

    /// <summary>
    /// DataSource GetAll with Expression filter.
    /// The deserializer automatically converts AcExpressionNode to Expression.
    /// Uses IQueryable.Where() instead of Compile() for EF Core compatibility.
    /// </summary>
    [SignalR(TestSignalRTags.ExpressionDataSourceGetAll)]
    public List<TestOrderItem_All_True> ExpressionDataSourceGetAll(Expression<Func<TestOrderItem_All_True, bool>>? filter = null)
    {
        if (filter == null)
            return _dataSourceItems.ToList();

        // Use AsQueryable().Where() instead of Compile() - this is how EF Core works
        return _dataSourceItems.AsQueryable().Where(filter).ToList();
    }

    /// <summary>
    /// DataSource GetAll with AcExpressionNode query parameter.
    /// Demonstrates EF Core-like usage where the client sends a query expression.
    /// The expression is rebuilt and applied to the server's IQueryable.
    /// </summary>
    [SignalR(TestSignalRTags.QueryableDataSourceGetAll)]
    public List<TestOrderItem_All_True> QueryableDataSourceGetAll(AcExpressionNode? queryNode = null)
    {
        // Simulate DbContext.Items (IQueryable<T>)
        IQueryable<TestOrderItem_All_True> serverQuery = _dataSourceItems.AsQueryable();

        // If client sent a query node, rebuild and apply it
        if (queryNode != null)
        {
            // If it's a lambda expression, use it directly as a filter
            if (queryNode.NodeType == ExpressionType.Lambda)
            {
                var filter = AcExpressionRebuilder.FromNode<TestOrderItem_All_True, bool>(queryNode);
                serverQuery = serverQuery.Where(filter);
            }
            else
            {
                // For more complex queries (Where chains, OrderBy, etc.)
                serverQuery = AcSerializerCommon.ApplyQueryFromNode(serverQuery, queryNode);
            }
        }

        // Materialize results - like .ToListAsync() in EF Core
        return serverQuery.ToList();
    }

    /// <summary>
    /// Executes an aggregate query (Count, Sum, Average, etc.) on the server.
    /// Returns a scalar value instead of a collection.
    /// This is the proper way to execute Count() - on the server, not client!
    /// </summary>
    [SignalR(TestSignalRTags.QueryableDataSourceAggregate)]
    public object? QueryableDataSourceAggregate(AcExpressionNode queryNode)
    {
        // Build the query from the expression node
        IQueryable<TestOrderItem_All_True> serverQuery = _dataSourceItems.AsQueryable();
        
        // Apply the full expression tree (including the aggregate method)
        var result = AcSerializerCommon.ExecuteQueryFromNode(serverQuery, queryNode);
        
        return result;
    }

    [SignalR(TestSignalRTags.DataSourceGetItem)]
    public TestOrderItem_All_True? DataSourceGetItem(int id) => _dataSourceItems.FirstOrDefault(x => x.Id == id);

    [SignalR(TestSignalRTags.DataSourceAdd)]
    public TestOrderItem_All_True DataSourceAdd(TestOrderItem_All_True item)
    {
        _dataSourceItems.Add(item);
        return item;
    }

    [SignalR(TestSignalRTags.DataSourceUpdate)]
    public TestOrderItem_All_True DataSourceUpdate(TestOrderItem_All_True item)
    {
        var index = _dataSourceItems.FindIndex(x => x.Id == item.Id);
        if (index >= 0) _dataSourceItems[index] = item;
        return item;
    }

    [SignalR(TestSignalRTags.DataSourceRemove)]
    public TestOrderItem_All_True? DataSourceRemove(TestOrderItem_All_True item)
    {
        var existing = _dataSourceItems.FirstOrDefault(x => x.Id == item.Id);
        if (existing != null) _dataSourceItems.Remove(existing);
        return existing;
    }

    #endregion

    #region StockTaking Production Bug Reproduction
    
    /// <summary>
    /// Simulates the exact production scenario from FruitBank GetStockTakings(false).
    /// Returns data from actual database records to reproduce the bug.
    /// Uses the REAL StockTaking model from StockTakingTestModels.cs
    /// </summary>
    [SignalR(TestSignalRTags.GetStockTakings)]
    public List<StockTaking> GetStockTakings(bool loadRelations)
    {
        // Exact data from production database:
        return
        [
            new StockTaking
            {
                Id = 7,
                StartDateTime = new DateTime(2025, 12, 3, 8, 55, 43, 539, DateTimeKind.Utc),
                IsClosed = false, // This is the key - IsClosed=false gets skipped by serializer!
                Creator = 6,
                Created = new DateTime(2025, 12, 3, 7, 55, 43, 571, DateTimeKind.Utc),
                Modified = new DateTime(2025, 12, 3, 7, 55, 43, 571, DateTimeKind.Utc),
                StockTakingItems = loadRelations ? [] : null
            },
            new StockTaking
            {
                Id = 6,
                StartDateTime = new DateTime(2025, 12, 2, 8, 21, 26, 439, DateTimeKind.Utc),
                IsClosed = true,
                Creator = 6,
                Created = new DateTime(2025, 12, 2, 7, 21, 26, 468, DateTimeKind.Utc),
                Modified = new DateTime(2025, 12, 2, 7, 21, 26, 468, DateTimeKind.Utc),
                StockTakingItems = loadRelations ? [] : null
            },
            new StockTaking
            {
                Id = 3,
                StartDateTime = new DateTime(2025, 11, 30, 14, 1, 55, 663, DateTimeKind.Utc),
                IsClosed = true,
                Creator = 6,
                Created = new DateTime(2025, 11, 30, 13, 1, 55, 692, DateTimeKind.Utc),
                Modified = new DateTime(2025, 11, 30, 13, 1, 55, 692, DateTimeKind.Utc),
                StockTakingItems = loadRelations ? [] : null
            },
            new StockTaking
            {
                Id = 2,
                StartDateTime = new DateTime(2025, 11, 30, 8, 20, 2, 182, DateTimeKind.Utc),
                IsClosed = true,
                Creator = 6,
                Created = new DateTime(2025, 11, 30, 7, 20, 3, 331, DateTimeKind.Utc),
                Modified = new DateTime(2025, 11, 30, 7, 20, 3, 331, DateTimeKind.Utc),
                StockTakingItems = loadRelations ? [] : null
            },
            new StockTaking
            {
                Id = 1,
                StartDateTime = new DateTime(2025, 11, 30, 8, 18, 59, 693, DateTimeKind.Utc),
                IsClosed = true,
                Creator = 6,
                Created = new DateTime(2025, 11, 30, 7, 19, 1, 849, DateTimeKind.Utc),
                Modified = new DateTime(2025, 11, 30, 7, 19, 1, 877, DateTimeKind.Utc),
                StockTakingItems = loadRelations ? [] : null
            }
        ];
    }

    #endregion

    #region Default Parameter Tests

    /// <summary>
    /// Method with two parameters where the second has a default value.
    /// Tests if the SignalR infrastructure correctly handles optional parameters.
    /// </summary>
    [SignalR(TestSignalRTags.TwoParamsWithDefault)]
    public string HandleTwoParamsWithDefault(int requiredParam, bool optionalParam = true)
    {
        return $"Required: {requiredParam}, Optional: {optionalParam}";
    }

    #endregion
}
