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

namespace HoneyBox.Tests.Services;

/// <summary>
/// 微信支付签名服务属性测试
/// Feature: payment-integration, Property 1: 支付签名正确性
/// </summary>
public class WechatPayServiceSignaturePropertyTests
{
    private readonly WechatPaySettings _settings;
    private readonly AppSettings _appSettings;
    private readonly Mock<ILogger<WechatPayService>> _mockLogger;
    private readonly Mock<IWechatPayConfigService> _mockConfigService;
    private readonly Mock<IWechatService> _mockWechatService;
    private readonly Mock<IRedisService> _mockRedisService;
    private readonly WechatPayService _wechatPayService;

    public WechatPayServiceSignaturePropertyTests()
    {
        _settings = new WechatPaySettings
        {
            DefaultMerchant = new WechatPayMerchantConfig
            {
                Name = "TestMerchant",
                MchId = "1234567890",
                AppId = "wx1234567890abcdef",
                Key = "test_secret_key_32_characters_ok",
                OrderPrefix = "TST",
                Weight = 1,
                NotifyUrl = "https://example.com/notify"
            },
            Merchants = new List<WechatPayMerchantConfig>
            {
                new WechatPayMerchantConfig
                {
                    Name = "Merchant1",
                    MchId = "1111111111",
                    AppId = "wx1111111111111111",
                    Key = "merchant1_secret_key_32_chars_ok",
                    OrderPrefix = "M01",
                    Weight = 1
                },
                new WechatPayMerchantConfig
                {
                    Name = "Merchant2",
                    MchId = "2222222222",
                    AppId = "wx2222222222222222",
                    Key = "merchant2_secret_key_32_chars_ok",
                    OrderPrefix = "M02",
                    Weight = 1
                }
            }
        };

        _mockLogger = new Mock<ILogger<WechatPayService>>();
        _mockConfigService = new Mock<IWechatPayConfigService>();
        _mockConfigService.Setup(x => x.GetMerchantByOrderNo(It.IsAny<string>()))
            .Returns(_settings.DefaultMerchant);
        _mockWechatService = new Mock<IWechatService>();
        _mockRedisService = new Mock<IRedisService>();

        _appSettings = new AppSettings { IsTestEnvironment = false };
        var options = Options.Create(_settings);
        var dbOptions = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        var dbContext = new HoneyBoxDbContext(dbOptions);

        _wechatPayService = new WechatPayService(
            dbContext,
            new HttpClient(),
            _mockLogger.Object,
            _mockConfigService.Object,
            _mockWechatService.Object,
            _mockRedisService.Object,
            options,
            _appSettings);
    }

    /// <summary>
    /// Property 1: 支付签名正确性
    /// For any set of payment parameters and secret key, generating a signature and then 
    /// verifying it with the same parameters should return true.
    /// Validates: Requirements 1.4, 7.1, 7.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureRoundTrip_ShouldVerifySuccessfully(
        NonEmptyString appId,
        NonEmptyString mchId,
        NonEmptyString nonceStr,
        PositiveInt totalFee,
        NonEmptyString outTradeNo)
    {
        // Arrange: Create a set of payment parameters
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", nonceStr.Item },
            { "body", "Test Payment" },
            { "out_trade_no", outTradeNo.Item },
            { "total_fee", totalFee.Item.ToString() },
            { "spbill_create_ip", "127.0.0.1" },
            { "trade_type", "JSAPI" },
            { "openid", "test_openid_123" }
        };

        // Act: Generate signature
        var sign = _wechatPayService.MakeSign(parameters);

        // Assert: Verify the signature
        return _wechatPayService.VerifySign(parameters, sign);
    }

    /// <summary>
    /// Property 1 (continued): 签名一致性
    /// For any set of parameters, generating the signature twice should produce the same result.
    /// Validates: Requirements 1.4, 7.1
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureConsistency_SameParametersShouldProduceSameSignature(
        NonEmptyString appId,
        NonEmptyString mchId,
        NonEmptyString nonceStr)
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", nonceStr.Item },
            { "body", "Test Payment" }
        };

        // Act: Generate signature twice
        var sign1 = _wechatPayService.MakeSign(parameters);
        var sign2 = _wechatPayService.MakeSign(parameters);

        // Assert: Both signatures should be identical
        return sign1 == sign2;
    }

    /// <summary>
    /// Property 1 (continued): 签名唯一性
    /// For different parameters, the signatures should be different.
    /// Validates: Requirements 7.1, 7.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureUniqueness_DifferentParametersShouldProduceDifferentSignatures(
        NonEmptyString appId1,
        NonEmptyString appId2)
    {
        // Skip if the two appIds are the same
        if (appId1.Item == appId2.Item)
            return true;

        // Arrange
        var parameters1 = new Dictionary<string, string>
        {
            { "appid", appId1.Item },
            { "mch_id", "1234567890" },
            { "nonce_str", "test_nonce" },
            { "body", "Test Payment" }
        };

        var parameters2 = new Dictionary<string, string>
        {
            { "appid", appId2.Item },
            { "mch_id", "1234567890" },
            { "nonce_str", "test_nonce" },
            { "body", "Test Payment" }
        };

        // Act
        var sign1 = _wechatPayService.MakeSign(parameters1);
        var sign2 = _wechatPayService.MakeSign(parameters2);

        // Assert: Signatures should be different
        return sign1 != sign2;
    }

    /// <summary>
    /// Property 1 (continued): 签名验证失败
    /// For any valid signature, modifying it should cause verification to fail.
    /// Validates: Requirements 7.2, 7.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureVerification_ModifiedSignatureShouldFail(
        NonEmptyString appId,
        NonEmptyString mchId)
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", "test_nonce" },
            { "body", "Test Payment" }
        };

        // Act: Generate valid signature
        var validSign = _wechatPayService.MakeSign(parameters);

        // Modify the signature (change first character)
        var modifiedSign = validSign.Length > 0
            ? (validSign[0] == 'A' ? 'B' : 'A') + validSign.Substring(1)
            : "INVALID";

        // Assert: Modified signature should fail verification
        return !_wechatPayService.VerifySign(parameters, modifiedSign);
    }

    /// <summary>
    /// Property 1 (continued): 空签名验证
    /// Empty or null signatures should always fail verification.
    /// Validates: Requirements 7.2, 7.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureVerification_EmptySignatureShouldFail(
        NonEmptyString appId,
        NonEmptyString mchId)
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", "test_nonce" }
        };

        // Assert: Empty and null signatures should fail
        var emptyFails = !_wechatPayService.VerifySign(parameters, "");
        var nullFails = !_wechatPayService.VerifySign(parameters, null!);

        return emptyFails && nullFails;
    }

    /// <summary>
    /// Property 1 (continued): 多商户签名支持
    /// For any merchant key, signatures generated with that key should only verify with the same key.
    /// Validates: Requirements 1.5, 7.3
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureMultiMerchant_DifferentKeysShouldProduceDifferentSignatures(
        NonEmptyString appId,
        NonEmptyString mchId)
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", "test_nonce" },
            { "body", "Test Payment" }
        };

        var key1 = _settings.DefaultMerchant.Key;
        var key2 = _settings.Merchants[0].Key;

        // Act: Generate signatures with different keys
        var sign1 = _wechatPayService.MakeSign(parameters, key1);
        var sign2 = _wechatPayService.MakeSign(parameters, key2);

        // Assert: Signatures should be different
        // And each signature should only verify with its own key
        var sign1VerifiesWithKey1 = _wechatPayService.VerifySign(parameters, sign1, key1);
        var sign1FailsWithKey2 = !_wechatPayService.VerifySign(parameters, sign1, key2);
        var sign2VerifiesWithKey2 = _wechatPayService.VerifySign(parameters, sign2, key2);
        var sign2FailsWithKey1 = !_wechatPayService.VerifySign(parameters, sign2, key1);

        return sign1 != sign2 
            && sign1VerifiesWithKey1 
            && sign1FailsWithKey2 
            && sign2VerifiesWithKey2 
            && sign2FailsWithKey1;
    }

    /// <summary>
    /// Property 1 (continued): 签名格式正确性
    /// Generated signatures should be 32-character uppercase hexadecimal strings (MD5 format).
    /// Validates: Requirements 1.4, 7.1
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureFormat_ShouldBe32CharUppercaseHex(
        NonEmptyString appId,
        NonEmptyString mchId,
        NonEmptyString nonceStr)
    {
        // Arrange
        var parameters = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", nonceStr.Item }
        };

        // Act
        var sign = _wechatPayService.MakeSign(parameters);

        // Assert: Signature should be 32 characters, uppercase, hexadecimal
        var isCorrectLength = sign.Length == 32;
        var isUppercase = sign == sign.ToUpper();
        var isHexadecimal = sign.All(c => "0123456789ABCDEF".Contains(c));

        return isCorrectLength && isUppercase && isHexadecimal;
    }

    /// <summary>
    /// Property 1 (continued): 参数顺序无关性
    /// The order of parameters should not affect the signature (due to sorting).
    /// Validates: Requirements 7.1
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureOrderIndependence_ParameterOrderShouldNotAffectSignature(
        NonEmptyString appId,
        NonEmptyString mchId,
        NonEmptyString nonceStr)
    {
        // Arrange: Create parameters in different orders
        var parameters1 = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "nonce_str", nonceStr.Item }
        };

        var parameters2 = new Dictionary<string, string>
        {
            { "nonce_str", nonceStr.Item },
            { "appid", appId.Item },
            { "mch_id", mchId.Item }
        };

        var parameters3 = new Dictionary<string, string>
        {
            { "mch_id", mchId.Item },
            { "nonce_str", nonceStr.Item },
            { "appid", appId.Item }
        };

        // Act
        var sign1 = _wechatPayService.MakeSign(parameters1);
        var sign2 = _wechatPayService.MakeSign(parameters2);
        var sign3 = _wechatPayService.MakeSign(parameters3);

        // Assert: All signatures should be identical
        return sign1 == sign2 && sign2 == sign3;
    }

    /// <summary>
    /// Property 1 (continued): 空值参数过滤
    /// Empty values should be filtered out and not affect the signature.
    /// Validates: Requirements 7.1
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SignatureEmptyValueFiltering_EmptyValuesShouldBeIgnored(
        NonEmptyString appId,
        NonEmptyString mchId)
    {
        // Arrange: Parameters with and without empty values
        var parametersWithEmpty = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item },
            { "empty_field", "" },
            { "null_field", null! }
        };

        var parametersWithoutEmpty = new Dictionary<string, string>
        {
            { "appid", appId.Item },
            { "mch_id", mchId.Item }
        };

        // Act
        var signWithEmpty = _wechatPayService.MakeSign(parametersWithEmpty);
        var signWithoutEmpty = _wechatPayService.MakeSign(parametersWithoutEmpty);

        // Assert: Signatures should be identical
        return signWithEmpty == signWithoutEmpty;
    }
}
