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 AuthServiceLoginRecordPropertyTests
{
    private HoneyBoxDbContext CreateInMemoryDbContext()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning))
            .Options;

        return new HoneyBoxDbContext(options);
    }

    private (AuthService authService, Mock<IIpLocationService> ipLocationMock) 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>>();

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

        return (authService, mockIpLocationService);
    }


    /// <summary>
    /// Property 15: 登录日志记录
    /// For any successful login, the system should:
    /// - Create a record in UserLoginLog table
    /// - Update UserAccount table with last login time and IP info
    /// Validates: Requirements 6.1, 6.3
    /// Feature: user-auth-migration, Property 15: 登录日志记录
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> LoginLogRecording()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, ipLocationMock) = CreateAuthService(dbContext);

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

        // Create a user
        var user = new User
        {
            OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
            Uid = "uid123",
            Nickname = "TestUser",
            HeadImg = "https://example.com/avatar.jpg",
            Status = 1,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        await dbContext.Users.AddAsync(user);

        // Create UserAccount for the user
        var userAccount = new UserAccount
        {
            UserId = user.Id,
            AccountToken = "token123",
            TokenNum = "num123",
            LastLoginIp = string.Empty
        };
        await dbContext.UserAccounts.AddAsync(userAccount);
        await dbContext.SaveChangesAsync();

        var device = "iOS";
        var clientIp = "192.168.1.1";

        // Act
        await authService.RecordLoginAsync(user.Id, device, clientIp);

        // Assert
        // 1. UserLoginLog should be created
        var loginLog = await dbContext.UserLoginLogs.FirstOrDefaultAsync(l => l.UserId == user.Id);
        var logCreated = loginLog != null && loginLog.Device == device;

        // 2. UserAccount should be updated
        var updatedAccount = await dbContext.UserAccounts.FirstOrDefaultAsync(ua => ua.UserId == user.Id);
        var accountUpdated = updatedAccount != null && 
                            updatedAccount.LastLoginTime != null &&
                            updatedAccount.IpProvince == "北京";

        return logCreated && accountUpdated;
    }

    /// <summary>
    /// Property 16: recordLogin接口返回值
    /// For any recordLogin call, the system should return:
    /// - User's uid
    /// - User's nickname
    /// - User's headimg
    /// Validates: Requirements 6.4
    /// Feature: user-auth-migration, Property 16: recordLogin接口返回值
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> RecordLoginReturnValue()
    {
        var dbContext = CreateInMemoryDbContext();
        var (authService, ipLocationMock) = CreateAuthService(dbContext);

        ipLocationMock.Setup(x => x.GetLocationAsync(It.IsAny<string>()))
            .ReturnsAsync(new IpLocationResult { Success = true });

        var expectedUid = "uid_" + Random.Shared.Next(1000, 9999);
        var expectedNickname = "TestUser_" + Random.Shared.Next(1000, 9999);
        var expectedHeadimg = "https://example.com/avatar_" + Random.Shared.Next(1000, 9999) + ".jpg";

        // Create a user
        var user = new User
        {
            OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
            Uid = expectedUid,
            Nickname = expectedNickname,
            HeadImg = expectedHeadimg,
            Status = 1,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        await dbContext.Users.AddAsync(user);
        await dbContext.SaveChangesAsync();

        // Act
        var result = await authService.RecordLoginAsync(user.Id, "Android", "192.168.1.1");

        // Assert
        return result.Uid == expectedUid &&
               result.Nickname == expectedNickname &&
               result.Headimg == expectedHeadimg;
    }


    /// <summary>
    /// Property 17: 账号注销类型处理
    /// For any log off request:
    /// - type=0 should deactivate the account (status=0)
    /// - type=1 should reactivate the account (status=1)
    /// Validates: Requirements 7.1, 7.2, 7.3
    /// Feature: user-auth-migration, Property 17: 账号注销类型处理
    /// </summary>
    [Property(MaxTest = 100)]
    public async Task<bool> LogOffTypeHandling()
    {
        // Test type=0 (deactivate)
        var dbContext1 = CreateInMemoryDbContext();
        var (authService1, _) = CreateAuthService(dbContext1);

        var user1 = new User
        {
            OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
            Uid = "uid123",
            Nickname = "TestUser",
            HeadImg = "https://example.com/avatar.jpg",
            Status = 1, // Active
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        await dbContext1.Users.AddAsync(user1);
        await dbContext1.SaveChangesAsync();

        // Act: Deactivate account
        await authService1.LogOffAsync(user1.Id, 0);

        // Assert: Status should be 0
        var deactivatedUser = await dbContext1.Users.FirstOrDefaultAsync(u => u.Id == user1.Id);
        var deactivateSuccess = deactivatedUser?.Status == 0;

        // Test type=1 (reactivate)
        var dbContext2 = CreateInMemoryDbContext();
        var (authService2, _) = CreateAuthService(dbContext2);

        var user2 = new User
        {
            OpenId = "openid_" + Guid.NewGuid().ToString().Substring(0, 8),
            Uid = "uid456",
            Nickname = "TestUser2",
            HeadImg = "https://example.com/avatar.jpg",
            Status = 0, // Inactive
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        await dbContext2.Users.AddAsync(user2);
        await dbContext2.SaveChangesAsync();

        // Act: Reactivate account
        await authService2.LogOffAsync(user2.Id, 1);

        // Assert: Status should be 1
        var reactivatedUser = await dbContext2.Users.FirstOrDefaultAsync(u => u.Id == user2.Id);
        var reactivateSuccess = reactivatedUser?.Status == 1;

        return deactivateSuccess && reactivateSuccess;
    }
}
