using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Admin.Business.Models.Goods;
using HoneyBox.Admin.Business.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

/// <summary>
/// GoodsService 属性测试
/// </summary>
public class GoodsServicePropertyTests
{
    private readonly Mock<ILogger<GoodsService>> _mockLogger = new();

    #region Property 8: Goods Stock Increase Prize Replication

    /// <summary>
    /// **Feature: admin-business-migration, Property 8: Goods Stock Increase Prize Replication**
    /// For any goods stock increase operation, the number of prize configurations for new sets 
    /// should equal the number of prize configurations in the first set.
    /// Validates: Requirements 5.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool StockIncrease_ShouldReplicatePrizeConfigurations(PositiveInt initialStock, PositiveInt stockIncrease, PositiveInt prizeCount)
    {
        // Limit values to reasonable ranges
        var actualInitialStock = (initialStock.Get % 10) + 1; // 1-10
        var actualStockIncrease = (stockIncrease.Get % 5) + 1; // 1-5
        var actualPrizeCount = (prizeCount.Get % 5) + 1; // 1-5 prizes

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

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new GoodsService(dbContext, _mockLogger.Object);

        // Create a goods item
        var goods = new Good
        {
            Title = "测试商品",
            ImgUrl = "http://test.com/img.jpg",
            ImgUrlDetail = "http://test.com/detail.jpg",
            Price = 100,
            Type = 1,
            Status = 1,
            Stock = actualInitialStock,
            SaleStock = 0,
            PrizeNum = actualPrizeCount,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        dbContext.Goods.Add(goods);
        dbContext.SaveChanges();

        // Create initial prize configurations
        var initialPrizes = new List<GoodsItem>();
        for (int i = 0; i < actualPrizeCount; i++)
        {
            initialPrizes.Add(new GoodsItem
            {
                GoodsId = goods.Id,
                Num = i + 1,
                Title = $"奖品{i + 1}",
                ImgUrl = $"http://test.com/prize{i + 1}.jpg",
                Stock = 1,
                SurplusStock = 1,
                Price = 50 + i * 10,
                Money = 30 + i * 5,
                ScMoney = 25 + i * 5,
                RealPro = 10,
                GoodsType = 1,
                Sort = i + 1,
                PrizeCode = GoodsService.GeneratePrizeCode(),
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now
            });
        }
        dbContext.GoodsItems.AddRange(initialPrizes);
        dbContext.SaveChanges();

        var initialPrizeCountInDb = dbContext.GoodsItems.Count(gi => gi.GoodsId == goods.Id);

        // Update goods with increased stock
        var updateRequest = new GoodsUpdateRequest
        {
            Title = goods.Title,
            Price = goods.Price,
            Type = goods.Type,
            ImgUrl = goods.ImgUrl,
            ImgUrlDetail = goods.ImgUrlDetail,
            Stock = actualInitialStock + actualStockIncrease // Increase stock
        };

        try
        {
            service.UpdateGoodsAsync(goods.Id, updateRequest, 1).GetAwaiter().GetResult();
        }
        catch
        {
            return false;
        }

        // Verify prize replication
        var finalPrizeCount = dbContext.GoodsItems.Count(gi => gi.GoodsId == goods.Id);
        var expectedPrizeCount = initialPrizeCountInDb + (actualStockIncrease * actualPrizeCount);

        return finalPrizeCount == expectedPrizeCount;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 8: Goods Stock Increase Prize Replication**
    /// For any goods with no initial prizes, stock increase should not create any new prizes.
    /// Validates: Requirements 5.6
    /// </summary>
    [Property(MaxTest = 100)]
    public bool StockIncrease_WithNoPrizes_ShouldNotCreatePrizes(PositiveInt initialStock, PositiveInt stockIncrease)
    {
        var actualInitialStock = (initialStock.Get % 10) + 1;
        var actualStockIncrease = (stockIncrease.Get % 5) + 1;

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

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new GoodsService(dbContext, _mockLogger.Object);

        // Create a goods item without prizes
        var goods = new Good
        {
            Title = "无奖品商品",
            ImgUrl = "http://test.com/img.jpg",
            ImgUrlDetail = "http://test.com/detail.jpg",
            Price = 100,
            Type = 1,
            Status = 1,
            Stock = actualInitialStock,
            SaleStock = 0,
            PrizeNum = 0,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        dbContext.Goods.Add(goods);
        dbContext.SaveChanges();

        // Update goods with increased stock
        var updateRequest = new GoodsUpdateRequest
        {
            Title = goods.Title,
            Price = goods.Price,
            Type = goods.Type,
            ImgUrl = goods.ImgUrl,
            ImgUrlDetail = goods.ImgUrlDetail,
            Stock = actualInitialStock + actualStockIncrease
        };

        try
        {
            service.UpdateGoodsAsync(goods.Id, updateRequest, 1).GetAwaiter().GetResult();
        }
        catch
        {
            return false;
        }

        // Verify no prizes were created
        var prizeCount = dbContext.GoodsItems.Count(gi => gi.GoodsId == goods.Id);
        return prizeCount == 0;
    }

    #endregion

    #region Property 9: Prize Code Uniqueness

    /// <summary>
    /// **Feature: admin-business-migration, Property 9: Prize Code Uniqueness**
    /// For any prize added to a box, the generated prize_code should be unique 
    /// within the entire goods_list table.
    /// Validates: Requirements 5.8
    /// </summary>
    [Property(MaxTest = 100)]
    public bool AddPrize_ShouldGenerateUniquePrizeCode(PositiveInt prizeCount)
    {
        var actualPrizeCount = (prizeCount.Get % 20) + 5; // 5-24 prizes

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

        using var dbContext = new HoneyBoxDbContext(options);
        var service = new GoodsService(dbContext, _mockLogger.Object);

        // Create a goods item
        var goods = new Good
        {
            Title = "测试商品",
            ImgUrl = "http://test.com/img.jpg",
            ImgUrlDetail = "http://test.com/detail.jpg",
            Price = 100,
            Type = 1,
            Status = 1,
            Stock = 100,
            SaleStock = 0,
            PrizeNum = 0,
            CreatedAt = DateTime.Now,
            UpdatedAt = DateTime.Now
        };
        dbContext.Goods.Add(goods);
        dbContext.SaveChanges();

        // Add multiple prizes
        var prizeIds = new List<int>();
        for (int i = 0; i < actualPrizeCount; i++)
        {
            var request = new PrizeCreateRequest
            {
                Title = $"奖品{i + 1}",
                ImgUrl = $"http://test.com/prize{i + 1}.jpg",
                Stock = 1,
                Price = 50,
                Money = 30,
                ScMoney = 25,
                RealPro = 10,
                GoodsType = 1
            };

            try
            {
                var prizeId = service.AddPrizeAsync(goods.Id, request).GetAwaiter().GetResult();
                prizeIds.Add(prizeId);
            }
            catch
            {
                return false;
            }
        }

        // Verify all prize codes are unique
        var prizeCodes = dbContext.GoodsItems
            .Where(gi => prizeIds.Contains(gi.Id))
            .Select(gi => gi.PrizeCode)
            .ToList();

        var uniqueCodes = prizeCodes.Distinct().Count();
        return uniqueCodes == prizeCodes.Count && prizeCodes.All(c => !string.IsNullOrEmpty(c));
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 9: Prize Code Uniqueness**
    /// For any two prizes added at different times, their prize codes should be different.
    /// Validates: Requirements 5.8
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GeneratePrizeCode_ShouldBeUnique(PositiveInt seed)
    {
        var codeCount = (seed.Get % 100) + 50; // 50-149 codes
        var codes = new HashSet<string>();

        for (int i = 0; i < codeCount; i++)
        {
            var code = GoodsService.GeneratePrizeCode();
            if (codes.Contains(code))
            {
                return false; // Duplicate found
            }
            codes.Add(code);
        }

        return codes.Count == codeCount;
    }

    /// <summary>
    /// **Feature: admin-business-migration, Property 9: Prize Code Uniqueness**
    /// Prize codes should follow the expected format (starting with "PC").
    /// Validates: Requirements 5.8
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GeneratePrizeCode_ShouldFollowFormat(PositiveInt seed)
    {
        var iterations = (seed.Get % 50) + 10;

        for (int i = 0; i < iterations; i++)
        {
            var code = GoodsService.GeneratePrizeCode();
            
            // Verify format: starts with "PC", followed by timestamp and random chars
            if (string.IsNullOrEmpty(code) || !code.StartsWith("PC") || code.Length < 20)
            {
                return false;
            }
        }

        return true;
    }

    #endregion
}
