using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Services;
using Microsoft.Extensions.Caching.Memory;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// 验证码服务属性测试
/// Feature: admin-system, Property 10: Captcha code characteristics
/// Validates: Requirements 14.2
/// </summary>
public class CaptchaServicePropertyTests
{
    private readonly IMemoryCache _cache;
    private readonly CaptchaService _captchaService;

    public CaptchaServicePropertyTests()
    {
        _cache = new MemoryCache(new MemoryCacheOptions());
        _captchaService = new CaptchaService(_cache);
    }

    /// <summary>
    /// Property 10: Captcha code characteristics
    /// For any generated captcha, the code SHALL be alphanumeric (letters and digits only) 
    /// and have a length between 4 and 6 characters inclusive.
    /// Validates: Requirements 14.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GeneratedCaptchaShouldHaveValidFormat()
    {
        var result = _captchaService.Generate();
        
        // CaptchaKey should not be empty
        if (string.IsNullOrWhiteSpace(result.CaptchaKey))
            return false;
        
        // CaptchaImage should be a valid base64 PNG image
        if (!result.CaptchaImage.StartsWith("data:image/png;base64,"))
            return false;
        
        // Verify the base64 part is valid
        var base64Part = result.CaptchaImage.Substring("data:image/png;base64,".Length);
        try
        {
            var bytes = Convert.FromBase64String(base64Part);
            if (bytes.Length == 0)
                return false;
        }
        catch
        {
            return false;
        }
        
        return true;
    }

    /// <summary>
    /// Property 10: Captcha code characteristics - Code format validation
    /// The captcha code stored in cache should be 4-6 alphanumeric characters.
    /// Validates: Requirements 14.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CaptchaCodeShouldBeAlphanumericAndCorrectLength()
    {
        var result = _captchaService.Generate();
        
        // Get the code from cache to verify its format
        var cacheKey = "captcha:" + result.CaptchaKey;
        if (!_cache.TryGetValue(cacheKey, out string? code))
            return false;
        
        if (string.IsNullOrEmpty(code))
            return false;
        
        // Length should be between 4 and 6
        if (code.Length < 4 || code.Length > 6)
            return false;
        
        // All characters should be alphanumeric
        foreach (var c in code)
        {
            if (!char.IsLetterOrDigit(c))
                return false;
        }
        
        return true;
    }

    /// <summary>
    /// Property 13: Captcha single-use enforcement
    /// For any captcha code, after ONE validation attempt (whether successful or failed), 
    /// the captcha SHALL be removed from cache and subsequent validation attempts 
    /// with the same captcha key SHALL fail.
    /// Validates: Requirements 14.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CaptchaShouldBeRemovedAfterValidation()
    {
        var result = _captchaService.Generate();
        var cacheKey = "captcha:" + result.CaptchaKey;
        
        // Get the actual code from cache
        _cache.TryGetValue(cacheKey, out string? actualCode);
        
        // First validation (with correct code) should succeed
        var firstValidation = _captchaService.Validate(result.CaptchaKey, actualCode ?? "wrong");
        
        // Second validation with same key should always fail (captcha removed)
        var secondValidation = _captchaService.Validate(result.CaptchaKey, actualCode ?? "wrong");
        
        // The captcha should be removed from cache after first validation
        var stillInCache = _cache.TryGetValue(cacheKey, out _);
        
        return !secondValidation && !stillInCache;
    }

    /// <summary>
    /// Property 13: Captcha single-use enforcement - Failed validation also removes captcha
    /// Even when validation fails, the captcha should be removed.
    /// Validates: Requirements 14.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool FailedValidationShouldAlsoRemoveCaptcha()
    {
        var result = _captchaService.Generate();
        var cacheKey = "captcha:" + result.CaptchaKey;
        
        // Validate with wrong code
        var validation = _captchaService.Validate(result.CaptchaKey, "WRONGCODE");
        
        // Validation should fail
        if (validation)
            return false;
        
        // Captcha should be removed from cache
        var stillInCache = _cache.TryGetValue(cacheKey, out _);
        
        return !stillInCache;
    }

    /// <summary>
    /// Captcha validation should be case-insensitive
    /// Validates: Requirements 14.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool CaptchaValidationShouldBeCaseInsensitive()
    {
        var result = _captchaService.Generate();
        var cacheKey = "captcha:" + result.CaptchaKey;
        
        // Get the actual code from cache
        if (!_cache.TryGetValue(cacheKey, out string? actualCode) || string.IsNullOrEmpty(actualCode))
            return false;
        
        // Generate a new captcha for testing case insensitivity
        var result2 = _captchaService.Generate();
        var cacheKey2 = "captcha:" + result2.CaptchaKey;
        
        if (!_cache.TryGetValue(cacheKey2, out string? actualCode2) || string.IsNullOrEmpty(actualCode2))
            return false;
        
        // Test with uppercase version
        var upperValidation = _captchaService.Validate(result2.CaptchaKey, actualCode2.ToUpper());
        
        // Generate another captcha for lowercase test
        var result3 = _captchaService.Generate();
        var cacheKey3 = "captcha:" + result3.CaptchaKey;
        
        if (!_cache.TryGetValue(cacheKey3, out string? actualCode3) || string.IsNullOrEmpty(actualCode3))
            return false;
        
        // Test with lowercase version
        var lowerValidation = _captchaService.Validate(result3.CaptchaKey, actualCode3.ToLower());
        
        // Both should succeed (case insensitive)
        return upperValidation && lowerValidation;
    }

    /// <summary>
    /// Invalid captcha key should fail validation
    /// Validates: Requirements 14.5
    /// </summary>
    [Property(MaxTest = 100)]
    public bool InvalidCaptchaKeyShouldFailValidation(NonEmptyString randomKey, NonEmptyString randomCode)
    {
        // Random key that doesn't exist should fail
        var validation = _captchaService.Validate(randomKey.Item, randomCode.Item);
        return !validation;
    }

    /// <summary>
    /// Empty or null inputs should fail validation
    /// Validates: Requirements 14.5
    /// </summary>
    [Fact]
    public void EmptyInputsShouldFailValidation()
    {
        var result = _captchaService.Generate();
        
        // Empty key should fail
        Assert.False(_captchaService.Validate("", "code"));
        Assert.False(_captchaService.Validate("   ", "code"));
        
        // Empty code should fail
        Assert.False(_captchaService.Validate(result.CaptchaKey, ""));
        Assert.False(_captchaService.Validate(result.CaptchaKey, "   "));
        
        // Both empty should fail
        Assert.False(_captchaService.Validate("", ""));
    }
}
