using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Core.Interfaces;
using HoneyBox.Core.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Auth;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// AuthService属性测试
/// 测试认证服务的核心属性
/// </summary>
public class AuthServicePropertyTests
{
    private HoneyBoxDbContext CreateInMemoryDbContext()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        return new HoneyBoxDbContext(options);
    }

    private (AuthService authService, Mock<IWechatService> wechatMock, Mock<IRedisService> redisMock) CreateAuthService(HoneyBoxDbContext dbContext)
    {
        var mockUserLogger = new Mock<ILogger<UserService>>();
        var userService = new UserService(dbContext, mockUserLogger.Object);

        var jwtSettings = new JwtSettings
        {
            Secret = "your-secret-key-must-be-at-least-32-characters-long-for-hs256",
            Issuer = "HoneyBox",
            Audience = "HoneyBoxUsers",
            ExpirationMinutes = 1440,
            RefreshTokenExpirationDays = 7
        };
        var mockJwtLogger = new Mock<ILogger<JwtService>>();
        var jwtService = new JwtService(jwtSettings, mockJwtLogger.Object);

        var mockWechatService = new Mock<IWechatService>();
        var mockIpLocationService = new Mock<IIpLocationService>();
        var mockRedisService = new Mock<IRedisService>();
        var mockAuthLogger = new Mock<ILogger<AuthService>>();

        // Default IP location result
        mockIpLocationService.Setup(x => x.GetLocationAsync(It.IsAny<string>()))
            .ReturnsAsync(new IpLocationResult { Success = true, Province = "北京", City = "北京", Adcode = "110000" });

        var authService = new AuthService(
            dbContext,
            userService,
            jwtService,
            mockWechatService.Object,
            mockIpLocationService.Object,
            mockRedisService.Object,
            jwtSettings,
            mockAuthLogger.Object);

        return (authService, mockWechatService, mockRedisService);
    }


    /// <summary>
    /// Property 1: 微信登录用户查找优先级
    /// For any login request with unionid, the system should prioritize finding user by unionid first,
    /// then by openid if unionid lookup fails.
    /// Validates: Requirements 1.2
    /// Feature: user-auth-migration, Property 1: 微信登录用户查找优先级
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> WechatLoginPrioritizesUnionIdLookup()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, wechatMock, redisMock) = CreateAuthService(dbContext);

        var openId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8);
        var unionId = "unionid_" + Guid.NewGuid().ToString().Substring(0, 8);

        // Setup: Create a user with unionid
        var existingUser = new User
        {
            OpenId = "different_openid",
            UnionId = unionId,
            Uid = "uid123",
            Nickname = "ExistingUser",
            HeadImg = "https://example.com/avatar.jpg",
            Status = 1,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        await dbContext.Users.AddAsync(existingUser);
        await dbContext.SaveChangesAsync();

        // Mock WeChat API to return the unionid
        wechatMock.Setup(x => x.GetOpenIdAsync(It.IsAny<string>()))
            .ReturnsAsync(new WechatAuthResult { Success = true, OpenId = openId, UnionId = unionId });

        // Mock Redis to allow login (no debounce)
        redisMock.Setup(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true);

        // Act
        var result = await authService.WechatMiniProgramLoginAsync("test_code", null, null);

        // Assert: Should find existing user by unionid, not create new one
        var usersCount = await dbContext.Users.CountAsync();
        return result.Success && result.UserId == existingUser.Id && usersCount == 1;
    }

    /// <summary>
    /// Property 4: 登录防抖机制
    /// For any user making repeated login requests within 3 seconds, 
    /// the second and subsequent requests should be rejected with "请勿频繁登录" error.
    /// Validates: Requirements 1.6, 2.6
    /// Feature: user-auth-migration, Property 4: 登录防抖机制
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> LoginDebounceRejectsRepeatedRequests()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, wechatMock, redisMock) = CreateAuthService(dbContext);

        var openId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8);

        // Mock WeChat API
        wechatMock.Setup(x => x.GetOpenIdAsync(It.IsAny<string>()))
            .ReturnsAsync(new WechatAuthResult { Success = true, OpenId = openId });

        // First request: Redis lock succeeds
        redisMock.SetupSequence(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true)   // First request succeeds
            .ReturnsAsync(false); // Second request fails (debounce)

        // First login should succeed
        var firstResult = await authService.WechatMiniProgramLoginAsync("test_code_1", null, null);

        // Second login should be rejected due to debounce
        var secondResult = await authService.WechatMiniProgramLoginAsync("test_code_1", null, null);

        return firstResult.Success && 
               !secondResult.Success && 
               secondResult.ErrorMessage == "请勿频繁登录";
    }


    /// <summary>
    /// Property 5: 推荐关系记录
    /// For any new user registration with a valid pid (recommender ID),
    /// the system should correctly save the pid to the user record.
    /// Validates: Requirements 1.8, 2.7
    /// Feature: user-auth-migration, Property 5: 推荐关系记录
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> RecommenderRelationshipIsRecorded(PositiveInt pid)
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, wechatMock, redisMock) = CreateAuthService(dbContext);

        var openId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8);

        // Mock WeChat API
        wechatMock.Setup(x => x.GetOpenIdAsync(It.IsAny<string>()))
            .ReturnsAsync(new WechatAuthResult { Success = true, OpenId = openId });

        // Mock Redis to allow login
        redisMock.Setup(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true);

        // Act: Login with pid
        var result = await authService.WechatMiniProgramLoginAsync("test_code", pid.Item, null);

        // Assert: User should have the correct pid
        var user = await dbContext.Users.FirstOrDefaultAsync(u => u.OpenId == openId);
        return result.Success && user != null && user.Pid == pid.Item;
    }

    /// <summary>
    /// Property 6: 验证码验证与清理
    /// For any mobile login request:
    /// - If verification code is correct, login should succeed and code should be deleted from Redis
    /// - If verification code is incorrect or expired, login should fail with "验证码错误"
    /// Validates: Requirements 2.1, 2.2
    /// Feature: user-auth-migration, Property 6: 验证码验证与清理
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> VerificationCodeValidationAndCleanup()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, wechatMock, redisMock) = CreateAuthService(dbContext);

        var mobile = "138" + Random.Shared.Next(10000000, 99999999).ToString();
        var correctCode = "123456";
        var wrongCode = "654321";

        // Mock Redis to allow login (no debounce)
        redisMock.Setup(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true);

        // Test 1: Correct code should succeed
        redisMock.Setup(x => x.GetStringAsync($"sms:code:{mobile}"))
            .ReturnsAsync(correctCode);
        redisMock.Setup(x => x.DeleteAsync($"sms:code:{mobile}"))
            .Returns(Task.CompletedTask);

        var successResult = await authService.MobileLoginAsync(mobile, correctCode, null, null);

        // Verify DeleteAsync was called (code cleanup)
        redisMock.Verify(x => x.DeleteAsync($"sms:code:{mobile}"), Times.Once);

        // Test 2: Wrong code should fail
        var dbContext2 = CreateInMemoryDbContext();
        var (authService2, _, redisMock2) = CreateAuthService(dbContext2);

        redisMock2.Setup(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true);
        redisMock2.Setup(x => x.GetStringAsync($"sms:code:{mobile}"))
            .ReturnsAsync(correctCode);

        var failResult = await authService2.MobileLoginAsync(mobile, wrongCode, null, null);

        return successResult.Success && 
               !failResult.Success && 
               failResult.ErrorMessage == "验证码错误";
    }


    /// <summary>
    /// Property 8: 数据库Token兼容存储
    /// For any successful login request, the system should store account_token in UserAccount table
    /// to maintain compatibility with the legacy system.
    /// Validates: Requirements 3.6
    /// Feature: user-auth-migration, Property 8: 数据库Token兼容存储
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> DatabaseTokenCompatibilityStorage()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, wechatMock, redisMock) = CreateAuthService(dbContext);

        var openId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8);

        // Mock WeChat API
        wechatMock.Setup(x => x.GetOpenIdAsync(It.IsAny<string>()))
            .ReturnsAsync(new WechatAuthResult { Success = true, OpenId = openId });

        // Mock Redis to allow login
        redisMock.Setup(x => x.TryAcquireLockAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<TimeSpan>()))
            .ReturnsAsync(true);

        // Act
        var result = await authService.WechatMiniProgramLoginAsync("test_code", null, null);

        // Assert: UserAccount should be created with account_token
        var userAccount = await dbContext.UserAccounts.FirstOrDefaultAsync(ua => ua.UserId == result.UserId);

        return result.Success && 
               userAccount != null && 
               !string.IsNullOrWhiteSpace(userAccount.AccountToken) &&
               !string.IsNullOrWhiteSpace(userAccount.TokenNum);
    }
}
