using System.Text.Json;
using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models.Config;
using HoneyBox.Admin.Business.Services;
using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// AdminConfigService 属性测试
/// </summary>
public class AdminConfigServicePropertyTests
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = false
    };

    private readonly Mock<ILogger<AdminConfigService>> _mockLogger = new();

    #region Property 1: Configuration Round-Trip Consistency

    /// <summary>
    /// **Feature: admin-business-migration, Property 1: Configuration Round-Trip Consistency**
    /// For any valid configuration object, saving it via UpdateConfigAsync and then retrieving it 
    /// via GetConfigAsync should produce an equivalent object.
    /// Validates: Requirements 3.1, 3.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool ConfigRoundTrip_ShouldPreserveData(NonEmptyString name, int value, bool enabled)
    {
        // Create test config data
        var configData = new TestConfigData
        {
            Name = name.Get,
            Value = value,
            Enabled = enabled
        };

        // Create a fresh service instance for each test
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var dbContext = new HoneyBoxDbContext(options);
        var mockRedis = new Mock<IRedisService>();
        mockRedis.Setup(x => x.GetStringAsync(It.IsAny<string>())).ReturnsAsync((string?)null);

        var service = new AdminConfigService(dbContext, mockRedis.Object, _mockLogger.Object);

        var configKey = $"test_config_{Guid.NewGuid():N}";

        // Save the config
        var saved = service.UpdateConfigAsync(configKey, configData).GetAwaiter().GetResult();
        if (!saved) return false;

        // Retrieve the config
        var retrieved = service.GetConfigAsync<TestConfigData>(configKey).GetAwaiter().GetResult();
        if (retrieved == null) return false;

        // Verify round-trip consistency
        return configData.Name == retrieved.Name &&
               configData.Value == retrieved.Value &&
               configData.Enabled == retrieved.Enabled;
    }

    #endregion

    #region Property 2: Merchant Prefix Uniqueness Validation

    /// <summary>
    /// **Feature: admin-business-migration, Property 2: Merchant Prefix Uniqueness Validation**
    /// For any weixinpay_setting configuration with duplicate merchant prefixes, 
    /// the validation should fail and return an error.
    /// Validates: Requirements 3.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool WeixinPaySetting_WithDuplicatePrefixes_ShouldFailValidation(PositiveInt seed)
    {
        var prefixes = new[] { "ABC", "DEF", "GHI", "JKL" };
        var prefix = prefixes[seed.Get % prefixes.Length];

        var setting = new WeixinPaySetting
        {
            Merchants = new List<WeixinPayMerchant>
            {
                new() { Name = "商户1", MchId = "123456", OrderPrefix = prefix },
                new() { Name = "商户2", MchId = "789012", OrderPrefix = prefix } // 重复前缀
            }
        };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json).GetAwaiter().GetResult();

        // Should return an error message containing "重复"
        return result != null && result.Contains("重复");
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 2: Merchant Prefix Uniqueness Validation**
    /// For any weixinpay_setting configuration with prefixes not exactly 3 characters, 
    /// the validation should fail and return an error.
    /// Validates: Requirements 3.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool WeixinPaySetting_WithInvalidPrefixLength_ShouldFailValidation(PositiveInt seed)
    {
        var invalidPrefixes = new[] { "AB", "A", "ABCD", "ABCDE", "" };
        var invalidPrefix = invalidPrefixes[seed.Get % invalidPrefixes.Length];

        var setting = new WeixinPaySetting
        {
            Merchants = new List<WeixinPayMerchant>
            {
                new() { Name = "商户1", MchId = "123456", OrderPrefix = invalidPrefix }
            }
        };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json).GetAwaiter().GetResult();

        // Should return an error message containing "3位字符"
        return result != null && result.Contains("3位字符");
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 2: Merchant Prefix Uniqueness Validation**
    /// For any weixinpay_setting configuration with valid unique 3-character prefixes, 
    /// the validation should pass.
    /// Validates: Requirements 3.4
    /// </summary>
    [Property(MaxTest = 100)]
    public bool WeixinPaySetting_WithValidUniquePrefixes_ShouldPassValidation(PositiveInt seed)
    {
        var allPrefixes = new[] { "ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX" };
        var count = (seed.Get % 3) + 1; // 1 to 3 merchants
        var startIndex = seed.Get % (allPrefixes.Length - count);

        var merchants = new List<WeixinPayMerchant>();
        for (int i = 0; i < count; i++)
        {
            merchants.Add(new WeixinPayMerchant
            {
                Name = $"商户{i + 1}",
                MchId = $"mch{i + 1}",
                OrderPrefix = allPrefixes[startIndex + i]
            });
        }

        var setting = new WeixinPaySetting { Merchants = merchants };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.WeixinPaySetting, json).GetAwaiter().GetResult();

        // Should return null (no error)
        return result == null;
    }

    #endregion

    #region Property 3: Default App Validation

    /// <summary>
    /// **Feature: admin-business-migration, Property 3: Default App Validation**
    /// For any miniprogram_setting configuration without at least one default app, 
    /// the validation should fail and return an error.
    /// Validates: Requirements 3.5
    /// </summary>
    [Property(MaxTest = 100)]
    public bool MiniprogramSetting_WithoutDefault_ShouldFailValidation(PositiveInt seed)
    {
        var prefixes = new[] { "AB", "CD", "EF", "GH" };
        var count = (seed.Get % 3) + 1; // 1 to 3 miniprograms

        var miniprograms = new List<MiniprogramConfig>();
        for (int i = 0; i < count; i++)
        {
            miniprograms.Add(new MiniprogramConfig
            {
                Name = $"小程序{i + 1}",
                AppId = $"wx{i + 1}",
                OrderPrefix = prefixes[i % prefixes.Length],
                IsDefault = 0 // 没有默认
            });
        }

        var setting = new MiniprogramSetting { Miniprograms = miniprograms };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.MiniprogramSetting, json).GetAwaiter().GetResult();

        // Should return an error message containing "默认"
        return result != null && result.Contains("默认");
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 3: Default App Validation**
    /// For any miniprogram_setting configuration with at least one default app, 
    /// the validation should pass.
    /// Validates: Requirements 3.5
    /// </summary>
    [Property(MaxTest = 100)]
    public bool MiniprogramSetting_WithDefault_ShouldPassValidation(PositiveInt seed)
    {
        var prefixes = new[] { "AB", "CD", "EF", "GH" };
        var count = (seed.Get % 3) + 1; // 1 to 3 miniprograms
        var defaultIndex = seed.Get % count;

        var miniprograms = new List<MiniprogramConfig>();
        for (int i = 0; i < count; i++)
        {
            miniprograms.Add(new MiniprogramConfig
            {
                Name = $"小程序{i + 1}",
                AppId = $"wx{i + 1}",
                OrderPrefix = prefixes[i % prefixes.Length],
                IsDefault = i == defaultIndex ? 1 : 0 // 设置一个默认
            });
        }

        var setting = new MiniprogramSetting { Miniprograms = miniprograms };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.MiniprogramSetting, json).GetAwaiter().GetResult();

        // Should return null (no error)
        return result == null;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 3: Default App Validation**
    /// For any h5_setting configuration without at least one default app, 
    /// the validation should fail and return an error.
    /// Validates: Requirements 3.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool H5Setting_WithoutDefault_ShouldFailValidation(PositiveInt seed)
    {
        var prefixes = new[] { "AB", "CD", "EF", "GH" };
        var count = (seed.Get % 3) + 1; // 1 to 3 H5 apps

        var h5Apps = new List<H5AppConfig>();
        for (int i = 0; i < count; i++)
        {
            h5Apps.Add(new H5AppConfig
            {
                Name = $"H5应用{i + 1}",
                OrderPrefix = prefixes[i % prefixes.Length],
                IsDefault = 0 // 没有默认
            });
        }

        var setting = new H5Setting { H5Apps = h5Apps };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.H5Setting, json).GetAwaiter().GetResult();

        // Should return an error message containing "默认"
        return result != null && result.Contains("默认");
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 3: Default App Validation**
    /// For any h5_setting configuration with at least one default app, 
    /// the validation should pass.
    /// Validates: Requirements 3.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool H5Setting_WithDefault_ShouldPassValidation(PositiveInt seed)
    {
        var prefixes = new[] { "AB", "CD", "EF", "GH" };
        var count = (seed.Get % 3) + 1; // 1 to 3 H5 apps
        var defaultIndex = seed.Get % count;

        var h5Apps = new List<H5AppConfig>();
        for (int i = 0; i < count; i++)
        {
            h5Apps.Add(new H5AppConfig
            {
                Name = $"H5应用{i + 1}",
                OrderPrefix = prefixes[i % prefixes.Length],
                IsDefault = i == defaultIndex ? 1 : 0 // 设置一个默认
            });
        }

        var setting = new H5Setting { H5Apps = h5Apps };

        var service = CreateService();
        var json = JsonSerializer.Serialize(setting, JsonOptions);
        var result = service.ValidateConfigAsync(ConfigKeys.H5Setting, json).GetAwaiter().GetResult();

        // Should return null (no error)
        return result == null;
    }

    #endregion

    #region Helper Methods

    private AdminConfigService CreateService()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        var dbContext = new HoneyBoxDbContext(options);
        var mockRedis = new Mock<IRedisService>();
        mockRedis.Setup(x => x.GetStringAsync(It.IsAny<string>())).ReturnsAsync((string?)null);

        return new AdminConfigService(dbContext, mockRedis.Object, _mockLogger.Object);
    }

    #endregion
}

/// <summary>
/// 测试用配置数据模型
/// </summary>
public class TestConfigData
{
    public string Name { get; set; } = string.Empty;
    public int Value { get; set; }
    public bool Enabled { get; set; }
}
