using System.Security.Cryptography;
using System.Text;
using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Core.Interfaces;
using HoneyBox.Core.Services;
using HoneyBox.Model.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// 微信支付 V3 签名属性测试
/// **Feature: wechat-pay-v3-upgrade**
/// </summary>
public class WechatPayV3SignaturePropertyTests
{
    // 测试用 RSA 密钥对（仅用于测试）
    private static readonly string TestPrivateKey;
    private static readonly string TestPublicKey;

    static WechatPayV3SignaturePropertyTests()
    {
        // 生成测试用 RSA 密钥对
        using var rsa = RSA.Create(2048);
        TestPrivateKey = ExportPrivateKeyPem(rsa);
        TestPublicKey = ExportPublicKeyPem(rsa);
    }

    private static string ExportPrivateKeyPem(RSA rsa)
    {
        var privateKeyBytes = rsa.ExportRSAPrivateKey();
        var base64 = Convert.ToBase64String(privateKeyBytes);
        var sb = new StringBuilder();
        sb.AppendLine("-----BEGIN RSA PRIVATE KEY-----");
        for (int i = 0; i < base64.Length; i += 64)
        {
            sb.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i)));
        }
        sb.AppendLine("-----END RSA PRIVATE KEY-----");
        return sb.ToString();
    }

    private static string ExportPublicKeyPem(RSA rsa)
    {
        var publicKeyBytes = rsa.ExportRSAPublicKey();
        var base64 = Convert.ToBase64String(publicKeyBytes);
        var sb = new StringBuilder();
        sb.AppendLine("-----BEGIN RSA PUBLIC KEY-----");
        for (int i = 0; i < base64.Length; i += 64)
        {
            sb.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i)));
        }
        sb.AppendLine("-----END RSA PUBLIC KEY-----");
        return sb.ToString();
    }

    private IWechatPayV3Service CreateService()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        var dbContext = new HoneyBoxDbContext(options);
        var httpClient = new HttpClient();
        var logger = Mock.Of<ILogger<WechatPayV3Service>>();
        var configService = Mock.Of<IWechatPayConfigService>();

        return new WechatPayV3Service(dbContext, httpClient, logger, configService);
    }

    #region Property 5: V3 请求签名正确性

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// *For any* V3 请求数据，使用相同的私钥和参数生成的签名应该是确定性的
    /// （相同输入产生相同输出）。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool V3Signature_SameInputs_ShouldProduceSameOutput(
        NonEmptyString method,
        NonEmptyString url,
        NonEmptyString body,
        PositiveInt seed)
    {
        var service = CreateService();

        // 使用固定的时间戳和随机串以确保确定性
        var timestamp = seed.Get.ToString();
        var nonce = $"nonce{seed.Get}";

        // 清理输入（移除可能导致问题的字符）
        var cleanMethod = method.Get.Replace("\n", "").Replace("\r", "").ToUpper();
        var cleanUrl = "/" + url.Get.Replace("\n", "").Replace("\r", "").TrimStart('/');
        var cleanBody = body.Get.Replace("\n", " ").Replace("\r", "");

        // 生成两次签名
        var signature1 = service.GenerateSignature(cleanMethod, cleanUrl, timestamp, nonce, cleanBody, TestPrivateKey);
        var signature2 = service.GenerateSignature(cleanMethod, cleanUrl, timestamp, nonce, cleanBody, TestPrivateKey);

        // 相同输入应该产生相同输出
        return signature1 == signature2;
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// *For any* V3 请求数据，生成的签名应该是有效的 Base64 字符串。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool V3Signature_ShouldBeValidBase64(
        NonEmptyString method,
        NonEmptyString url,
        NonEmptyString body,
        PositiveInt seed)
    {
        var service = CreateService();

        var timestamp = seed.Get.ToString();
        var nonce = $"nonce{seed.Get}";

        var cleanMethod = method.Get.Replace("\n", "").Replace("\r", "").ToUpper();
        var cleanUrl = "/" + url.Get.Replace("\n", "").Replace("\r", "").TrimStart('/');
        var cleanBody = body.Get.Replace("\n", " ").Replace("\r", "");

        var signature = service.GenerateSignature(cleanMethod, cleanUrl, timestamp, nonce, cleanBody, TestPrivateKey);

        // 验证是有效的 Base64 字符串
        try
        {
            var bytes = Convert.FromBase64String(signature);
            return bytes.Length > 0;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// *For any* V3 请求数据，不同的输入应该产生不同的签名。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool V3Signature_DifferentInputs_ShouldProduceDifferentOutput(
        NonEmptyString body1,
        NonEmptyString body2,
        PositiveInt seed)
    {
        // 如果两个 body 相同，跳过测试
        if (body1.Get == body2.Get) return true;

        var service = CreateService();

        var timestamp = seed.Get.ToString();
        var nonce = $"nonce{seed.Get}";
        var method = "POST";
        var url = "/v3/pay/transactions/jsapi";

        var cleanBody1 = body1.Get.Replace("\n", " ").Replace("\r", "");
        var cleanBody2 = body2.Get.Replace("\n", " ").Replace("\r", "");

        var signature1 = service.GenerateSignature(method, url, timestamp, nonce, cleanBody1, TestPrivateKey);
        var signature2 = service.GenerateSignature(method, url, timestamp, nonce, cleanBody2, TestPrivateKey);

        // 不同输入应该产生不同输出
        return signature1 != signature2;
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// *For any* V3 请求数据，签名应该可以使用对应的公钥验证。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool V3Signature_ShouldBeVerifiableWithPublicKey(
        NonEmptyString method,
        NonEmptyString url,
        NonEmptyString body,
        PositiveInt seed)
    {
        var service = CreateService();

        var timestamp = seed.Get.ToString();
        var nonce = $"nonce{seed.Get}";

        var cleanMethod = method.Get.Replace("\n", "").Replace("\r", "").ToUpper();
        var cleanUrl = "/" + url.Get.Replace("\n", "").Replace("\r", "").TrimStart('/');
        var cleanBody = body.Get.Replace("\n", " ").Replace("\r", "");

        // 生成签名
        var signature = service.GenerateSignature(cleanMethod, cleanUrl, timestamp, nonce, cleanBody, TestPrivateKey);

        // 构建签名字符串（与 GenerateSignature 方法中的格式一致）
        var signatureString = $"{cleanMethod}\n{cleanUrl}\n{timestamp}\n{nonce}\n{cleanBody}\n";

        // 使用公钥验证签名
        try
        {
            using var rsa = RSA.Create();
            rsa.ImportFromPem(TestPublicKey);

            var signatureBytes = Convert.FromBase64String(signature);
            return rsa.VerifyData(
                Encoding.UTF8.GetBytes(signatureString),
                signatureBytes,
                HashAlgorithmName.SHA256,
                RSASignaturePadding.Pkcs1);
        }
        catch
        {
            return false;
        }
    }

    #endregion

    #region 辅助方法测试

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// GenerateNonceStr 应该生成指定长度的随机字符串。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GenerateNonceStr_ShouldHaveCorrectLength(PositiveInt length)
    {
        var service = CreateService();
        var actualLength = Math.Min(Math.Max(length.Get % 64, 1), 64); // 限制在 1-64 之间

        var nonceStr = service.GenerateNonceStr(actualLength);

        return nonceStr.Length == actualLength;
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// GenerateNonceStr 应该只包含字母和数字。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GenerateNonceStr_ShouldContainOnlyAlphanumeric(PositiveInt seed)
    {
        var service = CreateService();
        var nonceStr = service.GenerateNonceStr(32);

        return nonceStr.All(c => char.IsLetterOrDigit(c));
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// GetTimestamp 应该返回有效的 Unix 时间戳。
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Fact]
    public void GetTimestamp_ShouldReturnValidUnixTimestamp()
    {
        var service = CreateService();
        var timestamp = service.GetTimestamp();

        // 应该是数字字符串
        Assert.True(long.TryParse(timestamp, out var timestampValue));

        // 应该是合理的时间戳（2020年之后，2100年之前）
        var minTimestamp = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds();
        var maxTimestamp = new DateTimeOffset(2100, 1, 1, 0, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds();

        Assert.True(timestampValue >= minTimestamp && timestampValue <= maxTimestamp);
    }

    #endregion

    #region 签名格式验证

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// 签名字符串格式应该符合微信 V3 规范：HTTP方法\nURL\n时间戳\n随机串\n请求体\n
    /// **Validates: Requirements 3.3**
    /// </summary>
    [Fact]
    public void V3Signature_Format_ShouldMatchWechatSpec()
    {
        var service = CreateService();

        var method = "POST";
        var url = "/v3/pay/transactions/jsapi";
        var timestamp = "1609459200";
        var nonce = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";
        var body = "{\"appid\":\"wx1234567890\",\"mchid\":\"1234567890\"}";

        // 生成签名
        var signature = service.GenerateSignature(method, url, timestamp, nonce, body, TestPrivateKey);

        // 验证签名不为空
        Assert.False(string.IsNullOrEmpty(signature));

        // 验证是有效的 Base64
        var signatureBytes = Convert.FromBase64String(signature);
        Assert.True(signatureBytes.Length > 0);

        // 验证签名长度（RSA-2048 签名应该是 256 字节）
        Assert.Equal(256, signatureBytes.Length);
    }

    /// <summary>
    /// **Feature: wechat-pay-v3-upgrade, Property 5: V3 请求签名正确性**
    /// 小程序支付签名格式应该符合微信规范：appId\n时间戳\n随机串\nprepay_id=xxx\n
    /// **Validates: Requirements 3.4**
    /// </summary>
    [Fact]
    public void GeneratePaySign_Format_ShouldMatchWechatSpec()
    {
        var service = CreateService();

        var appId = "wx1234567890";
        var timestamp = "1609459200";
        var nonceStr = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";
        var prepayId = "wx201410272009395522657a690389285100";

        // 生成支付签名
        var paySign = service.GeneratePaySign(appId, timestamp, nonceStr, prepayId, TestPrivateKey);

        // 验证签名不为空
        Assert.False(string.IsNullOrEmpty(paySign));

        // 验证是有效的 Base64
        var signatureBytes = Convert.FromBase64String(paySign);
        Assert.True(signatureBytes.Length > 0);

        // 验证签名长度（RSA-2048 签名应该是 256 字节）
        Assert.Equal(256, signatureBytes.Length);

        // 验证签名可以用公钥验证
        var signatureString = $"{appId}\n{timestamp}\n{nonceStr}\nprepay_id={prepayId}\n";
        using var rsa = RSA.Create();
        rsa.ImportFromPem(TestPublicKey);

        var isValid = rsa.VerifyData(
            Encoding.UTF8.GetBytes(signatureString),
            signatureBytes,
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);

        Assert.True(isValid);
    }

    #endregion
}
