using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models;
using HoneyBox.Admin.Business.Models.Order;
using HoneyBox.Admin.Business.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// OrderService 属性测试
/// </summary>
public class OrderServicePropertyTests
{
    private readonly Mock<ILogger<OrderService>> _mockLogger = new();

    #region Property 10: Order List Filter Accuracy

    /// <summary>
    /// **Feature: admin-business-migration, Property 10: Order List Filter Accuracy**
    /// For any order list request with filter parameters, all returned orders 
    /// should match all specified filter criteria.
    /// Validates: Requirements 6.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderListFilter_ByUserId_ShouldReturnOnlyMatchingOrders(PositiveInt userId, PositiveInt orderCount)
    {
        var actualUserId = (userId.Get % 10) + 1; // 1-10
        var actualOrderCount = (orderCount.Get % 20) + 5; // 5-24 orders

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed users
        SeedUsers(dbContext, 10);

        // Seed orders with various user IDs
        var random = new Random(userId.Get);
        for (int i = 0; i < actualOrderCount; i++)
        {
            var orderUserId = random.Next(1, 11); // Random user ID 1-10
            dbContext.Orders.Add(CreateOrder(orderUserId, $"ORD{i:D5}", 1));
        }
        dbContext.SaveChanges();

        // Query with user ID filter
        var request = new OrderListRequest { UserId = actualUserId, Page = 1, PageSize = 100 };
        var result = service.GetOrderListAsync(request).GetAwaiter().GetResult();

        // Verify all returned orders belong to the specified user
        return result.List.All(o => o.UserId == actualUserId);
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 10: Order List Filter Accuracy**
    /// For any order list request with status filter, all returned orders 
    /// should have the specified status.
    /// Validates: Requirements 6.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderListFilter_ByStatus_ShouldReturnOnlyMatchingOrders(PositiveInt statusSeed, PositiveInt orderCount)
    {
        var targetStatus = (byte)(statusSeed.Get % 3); // 0, 1, or 2
        var actualOrderCount = (orderCount.Get % 20) + 10; // 10-29 orders

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed users
        SeedUsers(dbContext, 5);

        // Seed orders with various statuses
        var random = new Random(statusSeed.Get);
        for (int i = 0; i < actualOrderCount; i++)
        {
            var status = (byte)random.Next(0, 3); // Random status 0-2
            dbContext.Orders.Add(CreateOrder(1, $"ORD{i:D5}", status));
        }
        dbContext.SaveChanges();

        // Query with status filter
        var request = new OrderListRequest { Status = targetStatus, Page = 1, PageSize = 100 };
        var result = service.GetOrderListAsync(request).GetAwaiter().GetResult();

        // Verify all returned orders have the specified status
        return result.List.All(o => o.Status == targetStatus);
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 10: Order List Filter Accuracy**
    /// For any order list request with order number filter, all returned orders 
    /// should contain the specified order number substring.
    /// Validates: Requirements 6.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderListFilter_ByOrderNum_ShouldReturnOnlyMatchingOrders(PositiveInt seed)
    {
        var orderCount = (seed.Get % 20) + 10; // 10-29 orders
        var searchPrefix = $"ORD{(seed.Get % 5):D2}"; // Search for ORD00, ORD01, etc.

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed users
        SeedUsers(dbContext, 3);

        // Seed orders with various order numbers
        for (int i = 0; i < orderCount; i++)
        {
            var orderNum = $"ORD{(i % 10):D2}{i:D3}"; // ORD00000, ORD01001, etc.
            dbContext.Orders.Add(CreateOrder(1, orderNum, 1));
        }
        dbContext.SaveChanges();

        // Query with order number filter
        var request = new OrderListRequest { OrderNum = searchPrefix, Page = 1, PageSize = 100 };
        var result = service.GetOrderListAsync(request).GetAwaiter().GetResult();

        // Verify all returned orders contain the search string
        return result.List.All(o => o.OrderNum.Contains(searchPrefix));
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 10: Order List Filter Accuracy**
    /// For any order list request with date range filter, all returned orders 
    /// should fall within the specified date range.
    /// Validates: Requirements 6.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderListFilter_ByDateRange_ShouldReturnOnlyMatchingOrders(PositiveInt seed)
    {
        var orderCount = (seed.Get % 15) + 10; // 10-24 orders

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed users
        SeedUsers(dbContext, 3);

        // Seed orders with various dates
        var baseDate = DateTime.Today;
        for (int i = 0; i < orderCount; i++)
        {
            var order = CreateOrder(1, $"ORD{i:D5}", 1);
            // Spread orders across -10 to +10 days
            var daysOffset = (i % 21) - 10;
            order.Addtime = (int)new DateTimeOffset(baseDate.AddDays(daysOffset)).ToUnixTimeSeconds();
            order.CreatedAt = baseDate.AddDays(daysOffset);
            dbContext.Orders.Add(order);
        }
        dbContext.SaveChanges();

        // Query with date range filter (last 5 days to today)
        // Note: The service adds 1 day to EndDate internally, so we use today as EndDate
        var startDate = baseDate.AddDays(-5);
        var endDate = baseDate; // Service will add 1 day, making it < baseDate.AddDays(1)
        var request = new OrderListRequest 
        { 
            StartDate = startDate, 
            EndDate = endDate, 
            Page = 1, 
            PageSize = 100 
        };
        var result = service.GetOrderListAsync(request).GetAwaiter().GetResult();

        // Verify all returned orders fall within the date range
        // Service filters: CreatedAt >= startDate AND CreatedAt < endDate.AddDays(1)
        var effectiveEndDate = endDate.AddDays(1);
        return result.List.All(o => o.CreatedAt >= startDate && o.CreatedAt < effectiveEndDate);
    }

    #endregion

    #region Property 11: Order Prize Grouping

    /// <summary>
    /// **Feature: admin-business-migration, Property 11: Order Prize Grouping**
    /// For any order detail request, the returned prize list should be correctly 
    /// grouped by prize_code with accurate counts.
    /// Validates: Requirements 6.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderDetail_PrizeGrouping_ShouldGroupByPrizeCode(PositiveInt prizeTypeCount, PositiveInt itemsPerType)
    {
        var actualPrizeTypes = (prizeTypeCount.Get % 5) + 2; // 2-6 prize types
        var actualItemsPerType = (itemsPerType.Get % 5) + 1; // 1-5 items per type

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed user
        SeedUsers(dbContext, 1);

        // Create order
        var order = CreateOrder(1, "ORD00001", 1);
        dbContext.Orders.Add(order);
        dbContext.SaveChanges();

        // Create order items with different prize codes
        var expectedGroups = new Dictionary<string, int>();
        for (int typeIndex = 0; typeIndex < actualPrizeTypes; typeIndex++)
        {
            var prizeCode = $"PC{typeIndex:D3}";
            expectedGroups[prizeCode] = actualItemsPerType;

            for (int itemIndex = 0; itemIndex < actualItemsPerType; itemIndex++)
            {
                dbContext.OrderItems.Add(new OrderItem
                {
                    OrderId = order.Id,
                    UserId = 1,
                    Status = 0,
                    GoodsId = 1,
                    Num = typeIndex * actualItemsPerType + itemIndex + 1,
                    ShangId = typeIndex + 1,
                    GoodslistId = typeIndex + 1,
                    GoodslistTitle = $"奖品类型{typeIndex + 1}",
                    GoodslistImgurl = $"http://test.com/prize{typeIndex + 1}.jpg",
                    GoodslistPrice = 100 + typeIndex * 50,
                    GoodslistMoney = 50 + typeIndex * 25,
                    GoodslistType = 1,
                    Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
                    PrizeCode = prizeCode,
                    CreatedAt = DateTime.Now,
                    UpdatedAt = DateTime.Now
                });
            }
        }
        dbContext.SaveChanges();

        // Get order detail
        var result = service.GetOrderDetailAsync(order.Id).GetAwaiter().GetResult();

        if (result == null) return false;

        // Verify grouping
        if (result.PrizeGroups.Count != actualPrizeTypes) return false;

        // Verify each group has correct count
        foreach (var group in result.PrizeGroups)
        {
            if (!expectedGroups.ContainsKey(group.PrizeCode)) return false;
            if (group.Count != expectedGroups[group.PrizeCode]) return false;
            if (group.Items.Count != expectedGroups[group.PrizeCode]) return false;
        }

        return true;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 11: Order Prize Grouping**
    /// For any order with items having the same prize_code, they should be grouped together.
    /// Validates: Requirements 6.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool OrderDetail_SamePrizeCode_ShouldBeGroupedTogether(PositiveInt seed)
    {
        var totalItems = (seed.Get % 20) + 5; // 5-24 items
        var prizeCodeCount = (seed.Get % 3) + 2; // 2-4 unique prize codes

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed user
        SeedUsers(dbContext, 1);

        // Create order
        var order = CreateOrder(1, "ORD00001", 1);
        dbContext.Orders.Add(order);
        dbContext.SaveChanges();

        // Create order items with random prize codes
        var random = new Random(seed.Get);
        var expectedCounts = new Dictionary<string, int>();
        
        for (int i = 0; i < totalItems; i++)
        {
            var prizeCodeIndex = random.Next(0, prizeCodeCount);
            var prizeCode = $"PC{prizeCodeIndex:D3}";
            
            if (!expectedCounts.ContainsKey(prizeCode))
                expectedCounts[prizeCode] = 0;
            expectedCounts[prizeCode]++;

            dbContext.OrderItems.Add(new OrderItem
            {
                OrderId = order.Id,
                UserId = 1,
                Status = 0,
                GoodsId = 1,
                Num = i + 1,
                ShangId = prizeCodeIndex + 1,
                GoodslistId = prizeCodeIndex + 1,
                GoodslistTitle = $"奖品{prizeCodeIndex + 1}",
                GoodslistImgurl = $"http://test.com/prize{prizeCodeIndex + 1}.jpg",
                GoodslistPrice = 100,
                GoodslistMoney = 50,
                GoodslistType = 1,
                Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
                PrizeCode = prizeCode,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            });
        }
        dbContext.SaveChanges();

        // Get order detail
        var result = service.GetOrderDetailAsync(order.Id).GetAwaiter().GetResult();

        if (result == null) return false;

        // Verify number of groups matches unique prize codes
        if (result.PrizeGroups.Count != expectedCounts.Count) return false;

        // Verify each group count matches expected
        foreach (var group in result.PrizeGroups)
        {
            if (!expectedCounts.ContainsKey(group.PrizeCode)) return false;
            if (group.Count != expectedCounts[group.PrizeCode]) return false;
        }

        // Verify total items count
        var totalGroupedItems = result.PrizeGroups.Sum(g => g.Count);
        return totalGroupedItems == totalItems;
    }

    #endregion

    #region Property 12: Shipping Order Cancellation Inventory Restoration

    /// <summary>
    /// **Feature: admin-business-migration, Property 12: Shipping Order Cancellation Inventory Restoration**
    /// For any shipping order cancellation, all prizes in the order should be restored 
    /// to the user's inventory (status changed from shipped to available).
    /// Validates: Requirements 6.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CancelShippingOrder_ShouldRestoreAllPrizes(PositiveInt itemCount)
    {
        var actualItemCount = (itemCount.Get % 10) + 2; // 2-11 items

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed user
        SeedUsers(dbContext, 1);

        // Create shipping order
        var sendNum = $"SEND{Guid.NewGuid():N}".Substring(0, 20);
        var shippingOrder = new OrderItemsSend
        {
            UserId = 1,
            SendNum = sendNum,
            Freight = 10,
            Status = 1, // 待发货
            Count = actualItemCount,
            Name = "测试用户",
            Mobile = "13800138001",
            Address = "测试地址",
            Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        dbContext.OrderItemsSends.Add(shippingOrder);
        dbContext.SaveChanges();

        // Create order items linked to shipping order
        var orderItemIds = new List<int>();
        for (int i = 0; i < actualItemCount; i++)
        {
            var orderItem = new OrderItem
            {
                OrderId = 1,
                UserId = 1,
                SendNum = sendNum,
                Status = 2, // 已发货状态
                GoodsId = 1,
                Num = i + 1,
                ShangId = 1,
                GoodslistId = 1,
                GoodslistTitle = $"奖品{i + 1}",
                GoodslistImgurl = "http://test.com/prize.jpg",
                GoodslistPrice = 100,
                GoodslistMoney = 50,
                GoodslistType = 1,
                Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
                PrizeCode = $"PC{i:D3}",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            };
            dbContext.OrderItems.Add(orderItem);
            dbContext.SaveChanges();
            orderItemIds.Add(orderItem.Id);
        }

        // Cancel shipping order
        try
        {
            var result = service.CancelShippingOrderAsync(shippingOrder.Id, 1).GetAwaiter().GetResult();
            if (!result) return false;
        }
        catch
        {
            return false;
        }

        // Verify shipping order status changed to cancelled
        var updatedShippingOrder = dbContext.OrderItemsSends.Find(shippingOrder.Id);
        if (updatedShippingOrder == null || updatedShippingOrder.Status != 4) return false;

        // Verify all order items are restored (status = 0, SendNum = null)
        var restoredItems = dbContext.OrderItems
            .Where(oi => orderItemIds.Contains(oi.Id))
            .ToList();

        return restoredItems.All(oi => oi.Status == 0 && oi.SendNum == null);
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 12: Shipping Order Cancellation Inventory Restoration**
    /// For any shipping order cancellation, the count of restored items should equal 
    /// the original shipping order item count.
    /// Validates: Requirements 6.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CancelShippingOrder_RestoredCount_ShouldMatchOriginal(PositiveInt seed)
    {
        var itemCount = (seed.Get % 8) + 3; // 3-10 items

        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new OrderService(dbContext, _mockLogger.Object);

        // Seed user
        SeedUsers(dbContext, 1);

        // Create shipping order
        var sendNum = $"SEND{Guid.NewGuid():N}".Substring(0, 20);
        var shippingOrder = new OrderItemsSend
        {
            UserId = 1,
            SendNum = sendNum,
            Freight = 10,
            Status = 1, // 待发货
            Count = itemCount,
            Name = "测试用户",
            Mobile = "13800138001",
            Address = "测试地址",
            Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        dbContext.OrderItemsSends.Add(shippingOrder);
        dbContext.SaveChanges();

        // Create order items linked to shipping order
        for (int i = 0; i < itemCount; i++)
        {
            dbContext.OrderItems.Add(new OrderItem
            {
                OrderId = 1,
                UserId = 1,
                SendNum = sendNum,
                Status = 2, // 已发货状态
                GoodsId = 1,
                Num = i + 1,
                ShangId = 1,
                GoodslistId = 1,
                GoodslistTitle = $"奖品{i + 1}",
                GoodslistImgurl = "http://test.com/prize.jpg",
                GoodslistPrice = 100,
                GoodslistMoney = 50,
                GoodslistType = 1,
                Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
                PrizeCode = $"PC{i:D3}",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            });
        }
        dbContext.SaveChanges();

        // Count items before cancellation
        var itemsBeforeCancellation = dbContext.OrderItems
            .Count(oi => oi.SendNum == sendNum && oi.Status == 2);

        // Cancel shipping order
        try
        {
            service.CancelShippingOrderAsync(shippingOrder.Id, 1).GetAwaiter().GetResult();
        }
        catch
        {
            return false;
        }

        // Count restored items after cancellation
        var restoredItems = dbContext.OrderItems
            .Count(oi => oi.Status == 0 && oi.SendNum == null);

        // The number of restored items should match the original count
        return restoredItems >= itemsBeforeCancellation;
    }

    #endregion

    #region Helper Methods

    private void SeedUsers(HoneyBoxDbContext dbContext, int count)
    {
        for (int i = 1; i <= count; i++)
        {
            dbContext.Users.Add(new User
            {
                Id = i,
                Uid = $"U{i:D3}",
                Nickname = $"测试用户{i}",
                Mobile = $"1380013800{i}",
                OpenId = $"openid{i}",
                HeadImg = $"http://test.com/head{i}.jpg",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            });
        }
        dbContext.SaveChanges();
    }

    private Order CreateOrder(int userId, string orderNum, byte status)
    {
        return new Order
        {
            UserId = userId,
            OrderNum = orderNum,
            OrderTotal = 100,
            OrderZheTotal = 90,
            Price = 90,
            UseMoney = 0,
            UseIntegral = 0,
            UseScore = 0,
            Zhe = 0.9m,
            GoodsId = 1,
            Num = 1,
            GoodsPrice = 100,
            GoodsTitle = "测试商品",
            PrizeNum = 1,
            Status = status,
            Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds(),
            PayTime = status == 1 ? (int)DateTimeOffset.Now.ToUnixTimeSeconds() : 0,
            PayType = status == 1 ? (byte)1 : (byte)0,
            OrderType = 0,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
    }

    #endregion
}
