using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Core.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>
/// PrizeAnnouncementService 属性测试（用户端）
/// 测试首页中大奖公告功能的用户端核心属性
/// </summary>
public class PrizeAnnouncementServicePropertyTests
{
    private readonly Mock<ILogger<PrizeAnnouncementService>> _mockLogger = new();

    private (HoneyBoxDbContext dbContext, PrizeAnnouncementService service) CreateService()
    {
        var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        var dbContext = new HoneyBoxDbContext(options);
        var service = new PrizeAnnouncementService(dbContext, _mockLogger.Object);
        return (dbContext, service);
    }

    /// <summary>
    /// 生成有效的非空字符串（用于必填字段）
    /// </summary>
    private static string GenerateValidString(string prefix, int seed)
    {
        return $"{prefix}_{Math.Abs(seed) % 1000}";
    }

    /// <summary>
    /// 创建测试用的公告实体
    /// </summary>
    private static PrizeAnnouncement CreateAnnouncement(int seed, bool isEnabled, int sort)
    {
        return new PrizeAnnouncement
        {
            UserAvatar = $"http://test.com/avatar_{seed}.jpg",
            UserName = GenerateValidString("User", seed),
            PrizeLevel = GenerateValidString("Level", seed),
            PrizeName = GenerateValidString("Prize", seed),
            Sort = sort,
            IsEnabled = isEnabled,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
    }

    #region Property 2: Enabled Filter Correctness

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 2: Enabled Filter Correctness**
    /// For any set of announcements with mixed enabled/disabled states, the user-facing API 
    /// should only return announcements where IsEnabled is true.
    /// **Validates: Requirements 2.2, 1.6**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool EnabledFilterCorrectness_OnlyReturnsEnabledAnnouncements(
        PositiveInt enabledCount,
        PositiveInt disabledCount)
    {
        var actualEnabledCount = Math.Max(1, enabledCount.Get % 10);
        var actualDisabledCount = disabledCount.Get % 10;

        var (dbContext, service) = CreateService();

        try
        {
            // Create enabled announcements
            for (int i = 0; i < actualEnabledCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            // Create disabled announcements
            for (int i = 0; i < actualDisabledCount; i++)
            {
                var announcement = CreateAnnouncement(i + 1000, isEnabled: false, sort: i + 1000);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: all returned items should be enabled
            // Since we're testing the service, we verify by checking the count matches enabled count
            return result.Count == actualEnabledCount;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 2: Enabled Filter Correctness**
    /// For any set of announcements, the count of returned items should equal 
    /// the count of enabled announcements in the database.
    /// **Validates: Requirements 2.2, 1.6**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool EnabledFilterCorrectness_CountMatchesEnabledInDatabase(PositiveInt totalCount)
    {
        var actualTotalCount = Math.Max(1, totalCount.Get % 20);
        var random = new Random(totalCount.Get);

        var (dbContext, service) = CreateService();

        try
        {
            var expectedEnabledCount = 0;

            // Create announcements with random enabled states
            for (int i = 0; i < actualTotalCount; i++)
            {
                var isEnabled = random.Next(2) == 1;
                if (isEnabled) expectedEnabledCount++;

                var announcement = CreateAnnouncement(i, isEnabled, sort: i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: count should match expected enabled count
            return result.Count == expectedEnabledCount;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 2: Enabled Filter Correctness**
    /// When all announcements are disabled, the API should return an empty list.
    /// **Validates: Requirements 2.2, 1.6**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool EnabledFilterCorrectness_AllDisabled_ReturnsEmptyList(PositiveInt count)
    {
        var actualCount = Math.Max(1, count.Get % 10);

        var (dbContext, service) = CreateService();

        try
        {
            // Create only disabled announcements
            for (int i = 0; i < actualCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: false, sort: i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: should return empty list
            return result.Count == 0;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 2: Enabled Filter Correctness**
    /// When all announcements are enabled, the API should return all of them.
    /// **Validates: Requirements 2.2, 1.6**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool EnabledFilterCorrectness_AllEnabled_ReturnsAll(PositiveInt count)
    {
        var actualCount = Math.Max(1, count.Get % 10);

        var (dbContext, service) = CreateService();

        try
        {
            // Create only enabled announcements
            for (int i = 0; i < actualCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: should return all announcements
            return result.Count == actualCount;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 2: Enabled Filter Correctness**
    /// When database is empty, the API should return an empty list.
    /// **Validates: Requirements 2.2, 1.6**
    /// </summary>
    [Fact]
    public void EnabledFilterCorrectness_EmptyDatabase_ReturnsEmptyList()
    {
        var (dbContext, service) = CreateService();

        try
        {
            // Get enabled announcements from empty database
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: should return empty list
            Assert.Empty(result);
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    #endregion

    #region Property 3: Sort Ordering Preservation

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 3: Sort Ordering Preservation**
    /// For any set of enabled announcements with different sort values, the user-facing API 
    /// should return them in ascending order by the sort field.
    /// **Validates: Requirements 2.3, 1.7**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SortOrderingPreservation_ReturnsInAscendingOrder(PositiveInt count, PositiveInt seed)
    {
        var actualCount = Math.Max(2, count.Get % 10 + 2);
        var random = new Random(seed.Get);

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcements with random sort values
            var sortValues = Enumerable.Range(0, actualCount)
                .Select(_ => random.Next(1000))
                .ToList();

            for (int i = 0; i < actualCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: sortValues[i]);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: for any two consecutive items, first item's sort should be <= second item's sort
            // Since DTO doesn't have Sort field, we verify by checking the order matches expected
            var expectedOrder = sortValues.OrderBy(s => s).ToList();
            
            // We need to verify the order by checking the UserName pattern which contains the index
            // The announcements should be returned in the order of their sort values
            for (int i = 0; i < result.Count - 1; i++)
            {
                // Extract the original index from UserName (format: "User_{index}")
                var currentIndex = int.Parse(result[i].UserName.Split('_')[1]);
                var nextIndex = int.Parse(result[i + 1].UserName.Split('_')[1]);
                
                var currentSort = sortValues[currentIndex];
                var nextSort = sortValues[nextIndex];
                
                if (currentSort > nextSort)
                {
                    return false;
                }
            }

            return true;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 3: Sort Ordering Preservation**
    /// For any two consecutive items in the result, the first item's sort value 
    /// should be less than or equal to the second item's sort value.
    /// **Validates: Requirements 2.3, 1.7**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SortOrderingPreservation_ConsecutiveItemsOrdered(PositiveInt count)
    {
        var actualCount = Math.Max(2, count.Get % 15 + 2);

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcements with sequential sort values in reverse order
            // This tests that the service correctly orders them
            for (int i = 0; i < actualCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: actualCount - i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: items should be in ascending order by sort
            // The first item should have sort = 1, last should have sort = actualCount
            for (int i = 0; i < result.Count - 1; i++)
            {
                var currentIndex = int.Parse(result[i].UserName.Split('_')[1]);
                var nextIndex = int.Parse(result[i + 1].UserName.Split('_')[1]);
                
                var currentSort = actualCount - currentIndex;
                var nextSort = actualCount - nextIndex;
                
                if (currentSort > nextSort)
                {
                    return false;
                }
            }

            return true;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 3: Sort Ordering Preservation**
    /// Announcements with the same sort value should be returned (order among them is stable).
    /// **Validates: Requirements 2.3, 1.7**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SortOrderingPreservation_SameSortValues_AllReturned(PositiveInt count)
    {
        var actualCount = Math.Max(2, count.Get % 10 + 2);

        var (dbContext, service) = CreateService();

        try
        {
            // Create announcements with the same sort value
            for (int i = 0; i < actualCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: 100);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: all announcements should be returned
            return result.Count == actualCount;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 3: Sort Ordering Preservation**
    /// Only enabled announcements should be sorted and returned.
    /// **Validates: Requirements 2.3, 1.7**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SortOrderingPreservation_OnlyEnabledAreSorted(
        PositiveInt enabledCount,
        PositiveInt disabledCount)
    {
        var actualEnabledCount = Math.Max(2, enabledCount.Get % 10 + 2);
        var actualDisabledCount = disabledCount.Get % 5;

        var (dbContext, service) = CreateService();

        try
        {
            // Create enabled announcements with specific sort values
            for (int i = 0; i < actualEnabledCount; i++)
            {
                var announcement = CreateAnnouncement(i, isEnabled: true, sort: actualEnabledCount - i);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            // Create disabled announcements with sort values that would appear first if included
            for (int i = 0; i < actualDisabledCount; i++)
            {
                var announcement = CreateAnnouncement(i + 1000, isEnabled: false, sort: 0);
                dbContext.PrizeAnnouncements.Add(announcement);
            }

            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: only enabled announcements are returned and sorted
            if (result.Count != actualEnabledCount)
            {
                return false;
            }

            // Verify ordering
            for (int i = 0; i < result.Count - 1; i++)
            {
                var currentIndex = int.Parse(result[i].UserName.Split('_')[1]);
                var nextIndex = int.Parse(result[i + 1].UserName.Split('_')[1]);
                
                var currentSort = actualEnabledCount - currentIndex;
                var nextSort = actualEnabledCount - nextIndex;
                
                if (currentSort > nextSort)
                {
                    return false;
                }
            }

            return true;
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    /// <summary>
    /// **Feature: homepage-prize-announcement, Property 3: Sort Ordering Preservation**
    /// Single announcement should be returned correctly.
    /// **Validates: Requirements 2.3, 1.7**
    /// </summary>
    [Property(MaxTest = 100)]
    public bool SortOrderingPreservation_SingleAnnouncement_ReturnsCorrectly(PositiveInt sort)
    {
        var actualSort = sort.Get % 1000;

        var (dbContext, service) = CreateService();

        try
        {
            // Create single enabled announcement
            var announcement = CreateAnnouncement(0, isEnabled: true, sort: actualSort);
            dbContext.PrizeAnnouncements.Add(announcement);
            dbContext.SaveChanges();

            // Get enabled announcements via service
            var result = service.GetEnabledAnnouncementsAsync().GetAwaiter().GetResult();

            // Verify: single announcement is returned
            return result.Count == 1 && result[0].UserName == "User_0";
        }
        finally
        {
            dbContext.Dispose();
        }
    }

    #endregion
}
