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

namespace HoneyBox.Tests.Services;

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

    #region Property 4: User List Pagination Consistency

    /// <summary>
    /// **Feature: admin-business-migration, Property 4: User List Pagination Consistency**
    /// For any valid page and pageSize, the returned list should have at most pageSize items,
    /// and the total count should be consistent across pages.
    /// Validates: Requirements 4.1
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserListPagination_ShouldReturnCorrectPageSize(PositiveInt seed)
    {
        var userCount = (seed.Get % 20) + 5; // 5 to 24 users
        var pageSize = (seed.Get % 5) + 1; // 1 to 5 per page
        var page = (seed.Get % 3) + 1; // page 1 to 3

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test users
        for (int i = 0; i < userCount; i++)
        {
            dbContext.Users.Add(CreateTestUser($"User{i}"));
        }
        dbContext.SaveChanges();

        var request = new UserListRequest { Page = page, PageSize = pageSize };
        var result = service.GetUserListAsync(request).GetAwaiter().GetResult();

        // Verify pagination consistency
        var expectedItemsOnPage = Math.Min(pageSize, Math.Max(0, userCount - (page - 1) * pageSize));
        
        return result.Total == userCount &&
               result.List.Count <= pageSize &&
               result.Page == page &&
               result.PageSize == pageSize;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 4: User List Pagination Consistency**
    /// The total count should remain consistent regardless of which page is requested.
    /// Validates: Requirements 4.1
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserListPagination_TotalShouldBeConsistentAcrossPages(PositiveInt seed)
    {
        var userCount = (seed.Get % 15) + 10; // 10 to 24 users
        var pageSize = 5;

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test users
        for (int i = 0; i < userCount; i++)
        {
            dbContext.Users.Add(CreateTestUser($"User{i}"));
        }
        dbContext.SaveChanges();

        // Get multiple pages
        var page1 = service.GetUserListAsync(new UserListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
        var page2 = service.GetUserListAsync(new UserListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
        var page3 = service.GetUserListAsync(new UserListRequest { Page = 3, PageSize = pageSize }).GetAwaiter().GetResult();

        // Total should be consistent across all pages
        return page1.Total == page2.Total && page2.Total == page3.Total && page1.Total == userCount;
    }

    #endregion

    #region Property 5: User List Filter Accuracy

    /// <summary>
    /// **Feature: admin-business-migration, Property 5: User List Filter Accuracy**
    /// When filtering by nickname, all returned users should contain the filter string in their nickname.
    /// Validates: Requirements 4.3
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserListFilter_ByNickname_ShouldReturnMatchingUsers(PositiveInt seed)
    {
        var filterNames = new[] { "Alice", "Bob", "Charlie", "David" };
        var filterName = filterNames[seed.Get % filterNames.Length];

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create users with different nicknames
        dbContext.Users.Add(CreateTestUser("Alice"));
        dbContext.Users.Add(CreateTestUser("AliceSmith"));
        dbContext.Users.Add(CreateTestUser("Bob"));
        dbContext.Users.Add(CreateTestUser("BobJones"));
        dbContext.Users.Add(CreateTestUser("Charlie"));
        dbContext.Users.Add(CreateTestUser("David"));
        dbContext.SaveChanges();

        var request = new UserListRequest { Nickname = filterName };
        var result = service.GetUserListAsync(request).GetAwaiter().GetResult();

        // All returned users should contain the filter string
        return result.List.All(u => u.Nickname != null && u.Nickname.Contains(filterName));
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 5: User List Filter Accuracy**
    /// When filtering by parent_id, all returned users should have that parent_id.
    /// Validates: Requirements 4.3
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserListFilter_ByParentId_ShouldReturnSubordinates(PositiveInt seed)
    {
        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create parent user
        var parent = CreateTestUser("Parent");
        dbContext.Users.Add(parent);
        dbContext.SaveChanges();

        // Create subordinate users
        var subordinateCount = (seed.Get % 5) + 1; // 1 to 5 subordinates
        for (int i = 0; i < subordinateCount; i++)
        {
            var child = CreateTestUser($"Child{i}");
            child.Pid = parent.Id;
            dbContext.Users.Add(child);
        }

        // Create other users without parent
        for (int i = 0; i < 3; i++)
        {
            dbContext.Users.Add(CreateTestUser($"Other{i}"));
        }
        dbContext.SaveChanges();

        var request = new UserListRequest { ParentId = parent.Id };
        var result = service.GetUserListAsync(request).GetAwaiter().GetResult();

        // All returned users should have the specified parent_id
        return result.Total == subordinateCount &&
               result.List.All(u => u.ParentId == parent.Id);
    }

    #endregion

    #region Property 6: User Balance Change Audit Trail

    /// <summary>
    /// **Feature: admin-business-migration, Property 6: User Balance Change Audit Trail**
    /// For any balance change operation, a corresponding record should be created in the profit table.
    /// Validates: Requirements 4.4
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserBalanceChange_ShouldCreateAuditRecord(PositiveInt seed)
    {
        var amount = (seed.Get % 100) + 1; // 1 to 100
        var isAdd = seed.Get % 2 == 0;

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test user with sufficient balance
        var user = CreateTestUser("TestUser");
        user.Money = 1000; // Ensure enough balance for subtraction
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = amount,
            Operation = isAdd ? OperationType.Add : OperationType.Subtract,
            Remark = "Property test"
        };

        var result = service.ChangeUserMoneyAsync(user.Id, request, 1).GetAwaiter().GetResult();
        if (!result) return false;

        // Verify audit record was created
        var auditRecord = dbContext.ProfitMoneys.FirstOrDefault(p => p.UserId == user.Id);
        
        return auditRecord != null &&
               auditRecord.ChangeMoney == (isAdd ? amount : -amount);
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 6: User Balance Change Audit Trail**
    /// After a balance change, the user's balance should reflect the change accurately.
    /// Validates: Requirements 4.4
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserBalanceChange_ShouldUpdateBalanceAccurately(PositiveInt seed)
    {
        var initialBalance = (seed.Get % 500) + 100; // 100 to 599
        var changeAmount = (seed.Get % 50) + 1; // 1 to 50
        var isAdd = seed.Get % 2 == 0;

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test user
        var user = CreateTestUser("TestUser");
        user.Money = initialBalance;
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        var request = new UserMoneyChangeRequest
        {
            Type = MoneyChangeType.Balance,
            Amount = changeAmount,
            Operation = isAdd ? OperationType.Add : OperationType.Subtract
        };

        service.ChangeUserMoneyAsync(user.Id, request, 1).GetAwaiter().GetResult();

        // Refresh user from database
        var updatedUser = dbContext.Users.Find(user.Id);
        var expectedBalance = isAdd ? initialBalance + changeAmount : initialBalance - changeAmount;

        return updatedUser!.Money == expectedBalance;
    }

    #endregion

    #region Property 7: User Status Toggle Consistency

    /// <summary>
    /// **Feature: admin-business-migration, Property 7: User Status Toggle Consistency**
    /// Toggling user status (ban/unban) should correctly update the status field.
    /// Validates: Requirements 4.5, 4.6
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserStatusToggle_ShouldUpdateStatusCorrectly(PositiveInt seed)
    {
        var initialStatus = (byte)(seed.Get % 2); // 0 or 1
        var newStatus = (byte)(1 - initialStatus); // Toggle

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test user
        var user = CreateTestUser("TestUser");
        user.Status = initialStatus;
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        // Toggle status
        var result = service.SetUserStatusAsync(user.Id, newStatus).GetAwaiter().GetResult();
        if (!result) return false;

        // Verify status was updated
        var updatedUser = dbContext.Users.Find(user.Id);
        return updatedUser!.Status == newStatus;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 7: User Status Toggle Consistency**
    /// Setting the same status multiple times should be idempotent.
    /// Validates: Requirements 4.5, 4.6
    /// </summary>
    [Property(MaxTest = 50)]
    public bool UserStatusToggle_ShouldBeIdempotent(PositiveInt seed)
    {
        var status = (byte)(seed.Get % 2); // 0 or 1

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test user
        var user = CreateTestUser("TestUser");
        user.Status = status;
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        // Set same status multiple times
        service.SetUserStatusAsync(user.Id, status).GetAwaiter().GetResult();
        service.SetUserStatusAsync(user.Id, status).GetAwaiter().GetResult();
        service.SetUserStatusAsync(user.Id, status).GetAwaiter().GetResult();

        // Verify status remains the same
        var updatedUser = dbContext.Users.Find(user.Id);
        return updatedUser!.Status == status;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 7: User Status Toggle Consistency**
    /// Test account flag toggle should correctly update the is_test field.
    /// Validates: Requirements 4.7
    /// </summary>
    [Property(MaxTest = 50)]
    public bool TestAccountToggle_ShouldUpdateFlagCorrectly(PositiveInt seed)
    {
        var initialIsTest = seed.Get % 2; // 0 or 1
        var newIsTest = 1 - initialIsTest; // Toggle

        using var dbContext = CreateDbContext();
        var service = new UserBusinessService(dbContext, _mockLogger.Object);

        // Create test user
        var user = CreateTestUser("TestUser");
        user.IsTest = initialIsTest;
        dbContext.Users.Add(user);
        dbContext.SaveChanges();

        // Toggle test account flag
        var result = service.SetTestAccountAsync(user.Id, newIsTest).GetAwaiter().GetResult();
        if (!result) return false;

        // Verify flag was updated
        var updatedUser = dbContext.Users.Find(user.Id);
        return updatedUser!.IsTest == newIsTest;
    }

    #endregion

    #region Helper Methods

    private HoneyBoxDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
            .Options;

        return new HoneyBoxDbContext(options);
    }

    private User CreateTestUser(string nickname)
    {
        return new User
        {
            OpenId = Guid.NewGuid().ToString("N"),
            Uid = $"UID{DateTime.Now.Ticks}{Guid.NewGuid():N}".Substring(0, 20),
            Nickname = nickname,
            HeadImg = "https://example.com/avatar.png",
            Mobile = $"138{new Random().Next(10000000, 99999999)}",
            Money = 100,
            Integral = 50,
            Score = 30,
            Status = 1,
            IsTest = 0,
            Pid = 0,
            Vip = 1,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
    }

    #endregion
}
