using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models.Finance;
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>
/// FinanceService 属性测试
/// </summary>
public class FinanceServicePropertyTests
{
    private readonly Mock<ILogger<FinanceService>> _mockLogger = new();

    #region Property 13: Consumption Ranking Sort Order

    /// <summary>
    /// **Feature: admin-business-migration, Property 13: Consumption Ranking Sort Order**
    /// For any consumption ranking request, the returned users should be sorted 
    /// by total consumption in descending order.
    /// Validates: Requirements 7.1
    /// </summary>
    [Property(MaxTest = 100)]
    public bool ConsumptionRanking_ShouldBeSortedByTotalConsumptionDescending(PositiveInt userCount, PositiveInt orderCount)
    {
        var actualUserCount = (userCount.Get % 10) + 2; // 2-11 users
        var actualOrderCount = (orderCount.Get % 30) + 10; // 10-39 orders

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

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

        // Seed users
        SeedUsers(dbContext, actualUserCount);

        // Seed orders with random amounts for random users
        var random = new Random(userCount.Get);
        for (int i = 0; i < actualOrderCount; i++)
        {
            var userId = random.Next(1, actualUserCount + 1);
            var price = random.Next(10, 1000);
            dbContext.Orders.Add(CreateOrder(userId, $"ORD{i:D5}", price));
        }
        dbContext.SaveChanges();

        // Get consumption ranking
        var request = new FinanceQueryRequest { Page = 1, PageSize = 100 };
        var result = service.GetConsumptionRankingAsync(request).GetAwaiter().GetResult();

        // Verify sorted by total consumption descending
        for (int i = 0; i < result.List.Count - 1; i++)
        {
            if (result.List[i].TotalConsumption < result.List[i + 1].TotalConsumption)
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 13: Consumption Ranking Sort Order**
    /// For any consumption ranking, the total consumption should equal the sum of 
    /// WeChat, balance, and integral payments.
    /// Validates: Requirements 7.1, 7.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool ConsumptionRanking_TotalShouldEqualSumOfPayments(PositiveInt seed)
    {
        var userCount = (seed.Get % 5) + 2; // 2-6 users
        var orderCount = (seed.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 FinanceService(dbContext, _mockLogger.Object);

        // Seed users
        SeedUsers(dbContext, userCount);

        // Seed orders
        var random = new Random(seed.Get);
        for (int i = 0; i < orderCount; i++)
        {
            var userId = random.Next(1, userCount + 1);
            var price = random.Next(100, 500);
            var useMoney = random.Next(0, price / 3);
            var useIntegral = random.Next(0, price / 3);
            
            dbContext.Orders.Add(new Order
            {
                UserId = userId,
                OrderNum = $"ORD{i:D5}",
                Price = price,
                UseMoney = useMoney,
                UseIntegral = useIntegral,
                UseScore = 0,
                Status = 1,
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now,
                GoodsId = 1,
                GoodsTitle = "商品",
                GoodsPrice = price,
                OrderTotal = price,
                OrderZheTotal = price,
                Zhe = 1,
                Num = 1,
                PrizeNum = 1,
                Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds()
            });
        }
        dbContext.SaveChanges();

        // Get consumption ranking
        var request = new FinanceQueryRequest { Page = 1, PageSize = 100 };
        var result = service.GetConsumptionRankingAsync(request).GetAwaiter().GetResult();

        // Verify total equals sum of payments
        foreach (var item in result.List)
        {
            var expectedTotal = item.WeChatPayment + item.BalancePayment + item.IntegralPayment;
            if (Math.Abs(item.TotalConsumption - expectedTotal) > 0.01m)
            {
                return false;
            }
        }

        return true;
    }

    #endregion

    #region Property 14: Financial Query Filter Accuracy

    /// <summary>
    /// **Feature: admin-business-migration, Property 14: Financial Query Filter Accuracy**
    /// For any balance detail query with user ID filter, all returned records 
    /// should belong to the specified user.
    /// Validates: Requirements 7.7
    /// </summary>
    [Property(MaxTest = 100)]
    public bool BalanceDetails_FilterByUserId_ShouldReturnOnlyMatchingRecords(PositiveInt userId, PositiveInt recordCount)
    {
        var actualUserId = (userId.Get % 5) + 1; // 1-5
        var actualRecordCount = (recordCount.Get % 20) + 10; // 10-29 records

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

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

        // Seed users
        SeedUsers(dbContext, 5);

        // Seed profit money records for multiple users
        var random = new Random(userId.Get);
        for (int i = 0; i < actualRecordCount; i++)
        {
            var recordUserId = random.Next(1, 6); // Random user 1-5
            dbContext.ProfitMoneys.Add(new ProfitMoney
            {
                UserId = recordUserId,
                ChangeMoney = 100,
                Money = 100,
                Type = 1,
                Content = $"变动{i}",
                ShareUid = 0,
                CreatedAt = DateTime.Now
            });
        }
        dbContext.SaveChanges();

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

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

    /// <summary>
    /// **Feature: admin-business-migration, Property 14: Financial Query Filter Accuracy**
    /// For any integral detail query with date range filter, all returned records 
    /// should fall within the specified date range.
    /// Validates: Requirements 7.7
    /// </summary>
    [Property(MaxTest = 100)]
    public bool IntegralDetails_FilterByDateRange_ShouldReturnOnlyMatchingRecords(PositiveInt seed)
    {
        var recordCount = (seed.Get % 20) + 10; // 10-29 records

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

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

        // Seed users
        SeedUsers(dbContext, 1);

        // Seed profit integral records with various dates
        var baseDate = DateTime.Today;
        for (int i = 0; i < recordCount; i++)
        {
            var daysOffset = (i % 21) - 10; // -10 to +10 days
            dbContext.ProfitIntegrals.Add(new ProfitIntegral
            {
                UserId = 1,
                ChangeMoney = 50,
                Money = 50,
                Type = 1,
                Content = $"积分{i}",
                ShareUid = 0,
                CreatedAt = baseDate.AddDays(daysOffset)
            });
        }
        dbContext.SaveChanges();

        // Query with date range filter (last 5 days)
        var startDate = baseDate.AddDays(-5);
        var endDate = baseDate;
        var request = new FinanceQueryRequest
        {
            StartDate = startDate,
            EndDate = endDate,
            Page = 1,
            PageSize = 100
        };
        var result = service.GetIntegralDetailsAsync(request).GetAwaiter().GetResult();

        // Verify all returned records fall within the date range
        var effectiveEndDate = endDate.AddDays(1);
        return result.List.All(r => r.CreatedAt >= startDate && r.CreatedAt < effectiveEndDate);
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 14: Financial Query Filter Accuracy**
    /// For any recharge record query with user ID filter, all returned records 
    /// should belong to the specified user.
    /// Validates: Requirements 7.7
    /// </summary>
    [Property(MaxTest = 100)]
    public bool RechargeRecords_FilterByUserId_ShouldReturnOnlyMatchingRecords(PositiveInt userId, PositiveInt recordCount)
    {
        var actualUserId = (userId.Get % 3) + 1; // 1-3
        var actualRecordCount = (recordCount.Get % 15) + 5; // 5-19 records

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

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

        // Seed users
        SeedUsers(dbContext, 3);

        // Seed profit pay records for multiple users
        var random = new Random(userId.Get);
        for (int i = 0; i < actualRecordCount; i++)
        {
            var recordUserId = random.Next(1, 4); // Random user 1-3
            dbContext.ProfitPays.Add(new ProfitPay
            {
                UserId = recordUserId,
                OrderNum = $"PAY{i:D5}",
                ChangeMoney = 100,
                Content = $"充值{i}",
                PayType = 0,
                CreatedAt = DateTime.Now
            });
        }
        dbContext.SaveChanges();

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

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

    #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, decimal price)
    {
        return new Order
        {
            UserId = userId,
            OrderNum = orderNum,
            Price = price,
            UseMoney = 0,
            UseIntegral = 0,
            UseScore = 0,
            Status = 1,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now,
            GoodsId = 1,
            GoodsTitle = "商品",
            GoodsPrice = price,
            OrderTotal = price,
            OrderZheTotal = price,
            Zhe = 1,
            Num = 1,
            PrizeNum = 1,
            Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds()
        };
    }

    #endregion
}
